Variadic模板包扩展

时间:2014-09-05 07:13:55

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

我正在尝试学习可变参数模板和函数。我无法理解为什么这段代码无法编译:

template<typename T>
static void bar(T t) {}

template<typename... Args>
static void foo2(Args... args)
{
    (bar(args)...);
}

int main()
{
    foo2(1, 2, 3, "3");
    return 0;    
}

编译时失败并显示错误:

  

错误C3520:&#39; args&#39;:参数包必须在此上下文中展开

(在函数foo2中)。

7 个答案:

答案 0 :(得分:107)

可以进行包扩展的地方之一是 braced-init-list 。您可以通过将扩展放在虚拟数组的初始化列表中来利用这一点:

template<typename... Args>
static void foo2(Args &&... args)
{
    int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
}

更详细地解释初始化程序的内容:

{ 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
  |       |       |                        |     |
  |       |       |                        |     --- pack expand the whole thing 
  |       |       |                        |   
  |       |       --perfect forwarding     --- comma operator
  |       |
  |       -- cast to void to ensure that regardless of bar()'s return type
  |          the built-in comma operator is used rather than an overloaded one
  |
  ---ensure that the array has at least one element so that we don't try to make an
     illegal 0-length array when args is empty

Demo

扩展{}的一个重要优势是它可以保证从左到右的评估。


使用C ++ 17 fold expressions,您只需编写

即可
((void) bar(std::forward<Args>(args)), ...);

答案 1 :(得分:37)

参数包只能在严格定义的上下文列表中展开,而运算符,不是其中之一。换句话说,不可能使用包扩展来生成由运算符,分隔的一系列子表达式组成的表达式。

经验法则是“扩展可以生成,列表 - ,列表分隔符的分隔模式。”运算符,不构造语法意义上的列表。

要为每个参数调用一个函数,可以使用递归(这是可变参数模板程序员框中的主要工具):

template <typename T>
void bar(T t) {}

void foo2() {}

template <typename Car, typename... Cdr>
void foo2(Car car, Cdr... cdr)
{
  bar(car);
  foo2(cdr...);
}

int main()
{
  foo2 (1, 2, 3, "3");
}

Live example

答案 2 :(得分:15)

SHAMELESS COPY [approved by its source]

参数包只能在严格定义的上下文列表中展开,而运算符,不是其中之一。换句话说,不可能使用包扩展来生成由运算符,分隔的一系列子表达式组成的表达式。

经验法则是&#34;扩展可以生成,列表 - 分隔的模式,其中,是列表分隔符。&#34;运算符,不构造语法意义上的列表。

要为每个参数调用一个函数,可以使用递归(这是可变参数模板程序员框中的主要工具):

#include <utility>

template<typename T>
void foo(T &&t){}

template<typename Arg0, typename Arg1, typename ... Args>
void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){
    foo(std::forward<Arg0>(arg0));
    foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}

auto main() -> int{
    foo(1, 2, 3, "3");
}

有用的非复制信息

在这个答案中你可能还没有看到的另一件事是使用&&说明符和std::forward。在C ++中,&&说明符可以表示以下两种情况之一:rvalue-references或universal references。

我不会进入rvalue-references,但是对于使用可变参数模板的人来说;普遍参考是一种上帝发送。

完美转发

std::forward和通用引用的一个用途是将类型转换为其他函数。

在您的示例中,如果我们将int&传递给foo2,则会因为在模板扣除后生成的int函数的签名而自动降级为foo2如果你想将这个arg转发给另一个可以通过引用修改它的函数,你将得到不希望的结果(变量不会被改变),因为foo2将传递一个引用通过传递int创建的临时文件。为了解决这个问题,我们指定了一个转发函数,将任何类型的引用转换为变量(rvalue 左值)。然后,为了确保我们传递转发函数中传递的确切类型,我们使用std::forward,然后,然后我们允许降级类型;因为我们现在处于最重要的位置。

如果您需要,请在universal referencesperfect forwarding上阅读更多内容; Scott Meyers作为一种资源非常出色。

答案 3 :(得分:3)

您可以使用make_tuple进行包扩展,因为它会引入扩展生成的,序列有效的上下文

make_tuple( (bar(std::forward<Args>(args)), 0)... );

现在,我怀疑编译器可以检测到生成的未使用/未命名/临时的零元组并进行优化。

Demo

答案 4 :(得分:3)

针对此问题的C ++ 17解决方案确实接近您的预期代码:

template<typename T>
static void bar(T t) {}

template<typename... Args>
static void foo2(Args... args) {
    (bar(args), ...);
}

int main() {
    foo2(1, 2, 3, "3");
    return 0;    
}

这将在每个表达式之间使用逗号运算符扩展模式

// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));

答案 5 :(得分:0)

这是一个完整的示例,基于此处的答案。

在JavaScript中复制console.log的示例:

Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");

文件名,例如js_console.h

#include <iostream>
#include <utility>

class Console {
protected:
    template <typename T>
    void log_argument(T t) {
        std::cout << t << " ";
    }
public:
    template <typename... Args>
    void log(Args&&... args) {
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void warn(Args&&... args) {
        cout << "WARNING: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void error(Args&&... args) {
        cout << "ERROR: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }
};

答案 6 :(得分:-1)

不保证执行顺序!

make_tuple( (bar(std::forward<Args>(args)), 0)... );

在此示例中,参数将以相反的顺序打印,至少使用GCC。

foo2(1, 2, 3, "3");

calling bar for 3
calling bar for 3
calling bar for 2
calling bar for 1