【问题标题】:C++ Variadic template method specializationC++ 可变参数模板方法特化
【发布时间】:2016-12-25 02:12:32
【问题描述】:

我有一些可变参数模板方法,看起来像这样:

    template<typename ... Args>
    void Invoke(const char* funcName, Args ... args) const;

    template<typename ... Args>
    void Invoke(const char* funcName, Args ... args) const
    {
        SPrimitive params[] = { args ... };
        SomeOtherInvoke(funcName, params, sizeof ... (Args));
    }

这里是 SPrimitive - 只是一个简单的结构,其中包含任何原始类型的构造函数。

我想为某些复杂类型再做一个 Invoke 定义。这是我的问题: 是否可以在 c++ 11/14 中进行可变参数模板方法专业化? 我的意思是这样的(为简单起见,让我的类型为 int):

    template<int ... Args>
    void Invoke(const char* funcName, Args ... args)
    {
        int params[] = { args ... };
        SomeComplexInvoke(funcName, params, sizeof ... (Args));
    }

这里我想要一个特殊化,它接受任何 int 类型的参数计数,所以我可以这样调用它:

    Invoke("method", 2, 4 ,9);

【问题讨论】:

  • 没有专业化,但你可以使用重载和 SFINAE。
  • std::initializer_list&lt;T&gt; 似乎更合适(使用Invoke("method" {2, 4, 9}) 语法)。
  • @Jarod42 我见过template&lt;int... args&gt; elsewhere;不能以某种方式将其用于专用于可变数量 int 参数的可变参数函数模板吗?

标签: c++ c++11 variadic-templates


【解决方案1】:

正如@Jarod42 提到的,不应该通过专业化来完成。在你的例子中,如果所有参数类型都是int,你想要一些特别的东西,所以让我们编写一个模板来检查它:

template<typename ref, typename t, typename ...types>
struct all_same {
        static constexpr bool value = std::is_same<ref, t>::value && all_same<ref, types...>::value;
};

template<typename ref, typename t>
struct all_same<ref, t> {
        static constexpr bool value = std::is_same<ref, t>::value;
};

它检查第一个类型参数是否等于所有其他类型参数。那么在Invoke中,我们应该根据args...类型选择params类型:

template<typename ... Args>
void Invoke(const char* funcName, Args ... args)
{
    using params_type = typename std::conditional<all_same<int, Args...>::value, int, SPrimitive>::type;
    params_type params[] = { args ... };
    SomeOtherInvoke(funcName, params, sizeof ... (Args));
}

现在为了演示,让我们定义:

struct SPrimitive{
};

void SomeOtherInvoke(const char*, SPrimitive*, size_t) {
        std::cout << "Invoked for SPrimitive\n";
}

void SomeOtherInvoke(const char*, int*, size_t) {
        std::cout << "Invoked for int\n";
}

然后打电话

Invoke("foo", SPrimitive());
Invoke("foo", SPrimitive(), SPrimitive());
Invoke("foo", 1, 2, 3, 4);

输出是:

Invoked for SPrimitive
Invoked for SPrimitive
Invoked for int

这是你要求的。

【讨论】:

  • 谢谢,这正是我要找的!
【解决方案2】:

我也遇到了这个问题,找到了一个更简单的方法。这个想法是只使用一个简单的 static_cast。

template <class ...args
void test (args ...a)
{
 test___( sizeof... (a), static_cast<int*>(&a)... );
}

extern void test___(int count,...);

注意:

  • 我有意使用 c 省略号来表明您可以简单地将 cpp 可变参数传递给 c 省略号参数。这个cpp通用吗?我不知道。

  • 还让您知道使用两全其美是多么方便。 Cpp 可变参数函数编写起来很笨重。 C 省略号不知道类型,这真的很重要。

  • 最后注意我们如何在可变参数上使用操作符的地址。这个cpp通用吗?我不知道。

我想以 c 省略号笨拙而 cpp 结合 c 救了我的情况结束。

//This function expects RECT pointers
void addRect (int count,...);

int main () 
{
 RECT r = {0,0,640,480};
 addRect (3,r,r,r}; // However it is so easy to forget that
                              // program will crash
}

因此,改为将公共方法编写为可变参数函数,从而将正确的东西传递给省略号版本。

注意:一切都可以通过 cpp variadics 完成,但它很笨重,而且很多时候看起来很糟糕。

【讨论】:

  • 是的,只要首先支持参数类型,就可以将扩展参数包传递给 C 可变参数函数。模板实例化只是将包扩展转换为表达式列表,然后调用 C 可变参数函数的规则照常适用。
  • 但是 C 可变参数函数有一些限制,这也让它们很尴尬:传递任何非平凡的类类型只是有条件地支持。如果您想处理不同的可能参数类型,您需要将类型列表从编译时信息编码到运行时,然后在实现中对其进行解码(最好把它做好!)。我真的无法想象在很多情况下我宁愿使用&lt;cstdarg&gt; 而不是可变参数模板。
  • 如果传递的任何参数不是int(包括unsigned int),您的第一个函数将无法编译。这似乎对现有通用函数的重载没有帮助。
  • 如果参数不能转换为 int 则无法编译是我的 static_cast 的目的。不,它肯定不会失败 unsigned int 因为 unsigned int 可以通过提升转换为 int 。你测试过吗?我没有,但我确信它有效。了解在模板内调用的任何代码都已经实例化,因此不再受模板转换的限制。
  • static_castunsigned int*int* 格式不正确。要获取 int 对象,您需要一个本地数组。我认为reinterpret_castunsigned int* 上是合法的,但会导致任何其他类型的未定义行为。
【解决方案3】:

我已经给出了另一个答案。现在这是另一个。请注意,我仍然相信做出第一个答案的人;并且他的回答作为智力演示很有用,并且可能有它的位置和时间。

注意:我的替代方案接受任何可转换为 int 的内容。虽然他的建议只接受 int 而不会接受 int& 除非你使用 std::remove_reference。比较复杂。

此替代方案基于模板重载。如果我们想要一个只接受整数或可转换为整数的对象的函数;我们只需添加 int 作为可变参数模板函数的第一个参数!

template <class ...args>
void example (int i,args ...rest)
{
 example__(sizeof... (rest)+1, i, rest...);
}

void example (int count,...);

注意:再一次,一切都可以用 Cpp Variadics 完成。但我认为 C 省略号确实使任务更简单,编译错误更少,如果你记得只传递原语和指向 C 省略号的指针,则运行时错误的可能性很小。

如果您希望您的函数在一个函数调用中接受多种类型,请执行以下操作。我将省略私有实现函数和公共实现。

template <class ...args>
void example (int i,args &...rest);

template <class ...args>
void example (const std::string&, args &...rest);

最后,如果您希望您的函数接受各种类型,但在单独的函数调用中,我建议使用命名约定。

template <class ...args>
void intExample (int i,args &...rest);

template <class ...args>
void stringExample (const std::string&, args &...rest);

template <class ...args>
int getInt (int i,args &...rest);

template <class ...args>
std::string getString (const std::string&, args &...rest);

注意:通过在最终示例中保留第一个参数,您可以确保必须将至少一个参数传递给您的函数。

好了,今天就到这里。如果你做到了这一步,你就很棒了。谢谢收看。如果你喜欢我的视频;单击该订阅按钮。粉碎那个like按钮。

编辑_ 我在 Cpp Variadics 中发现了一个美,我想在这里展示。如您所知,Cpp 模板是一个漂亮的 C 宏,它是类型安全且尊重命名空间的。

struct ColumnStyle
{
 int type;
 int align;
 int width;
 int min_width;
 int max_width;
 Int margin;
};

我想扩展这个类,但我希望它仍然作为一个聚合类。

struct LogColumn : ColumnStyle
{
 template <class ...args>
 LogColumn (const wchar_t *caption, HBITMAP icon, args ...style)
  : ColumnStyle {style...}, // amazing! I can simply pass the variadic arguments to the aggregate ctor!
    header_caption (caption),
    header_icon (icon)
  {}

 const wchart_t *header_caption;
 HBITMAP header_icon;
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-15
    • 1970-01-01
    • 2017-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多