可变参数模板包扩展


78

我正在尝试学习可变参数的模板和功能。我不明白为什么这段代码无法编译:

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:“ args”:必须在这种情况下扩展参数包

(在功能上foo2)。


1
如果Args是空的怎么办?
CoffeeandCode 2014年

Answers:


129

可以进行数据包扩展的位置之一是在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

演示

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


使用C ++ 17 fold表达式,您可以编写

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

19
我见过的一个变体是,using expander = int[]; expander{...};因为数组变量没有名称,并且对于编译器来说,显而易见的是,不需要我创建或使用数组。
Mooing Duck

4
第二个0是什么?逗号运算符后面的那个
Aaron McDaid 2015年

4
@AaronMcDaid这样整个表达式的类型就是int并匹配数组的元素类型。
TC

我注意到有人会写operator void ()(是的,疯了,我知道)。但是不,我并不是说强制转换void是一个坏主意。只是想着想尝试扩大副作用最小的包装时可能发生的疯狂事情的​​乐趣
Aaron McDaid 2015年

2
@AaronMcDaid(void)永远不会operator void()(感谢)调用
TC

40

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

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

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

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");
}

现场例子


2
该死的,你只是打赌我回答这个动摇的拳头,但是你可能应该为你的答案添加完美的转发。这也是“可变参数模板编程器中的主要工具”。
CoffeeandCode 2014年

谢谢您的回答。我知道递归实现。我只想找到解决方法来编译没有递归和新功能的代码。
Viacheslav Dronov

1
@ViacheslavDronov好像您正在使用模板:您已经有一些编译器正在生成的函数,为什么不在该列表上添加一个呢?
CoffeeandCode 2014年

@CoffeeandCode我正式授予您复制我的答案的权限,并通过添加和说明完善的转发功能扩展其答案。
Angew不再为2014年

5
您可以使用虚拟数组轻松进行扩展。int dummy[] = { 0, ((void) bar(std::forward<Args>(args)),0)... };
TC 2014年

17

SHAMELESS COPY [由其来源批准]

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

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

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

#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或通用引用。

我不会涉及右值引用,而是会介绍使用可变参数模板的人。通用引用是天赐之物。

完美转发

std::forward通用引用的用途之一是将类型完美地转发到其他函数。

在您的示例中,如果我们将传递int&foo2它,则int由于foo2模板推导后生成函数的签名,它会被自动降级为;如果您想将其转发arg给另一个可以通过引用对其进行修改的函数,则会得到不希望的结果(该变量将不会更改),因为foo2它将传递对通过传递int给它的临时文件的引用。为了解决这个问题,我们指定了一个转发函数以对变量(rvaluelvalue)进行任何类型的引用。然后,要确保我们传递在转发函数中传递的确切类型,则只能使用std::forward那我们允许降级类型吗?因为我们现在才是最重要的时刻。

如果需要,请阅读有关通用参考完善转发的更多信息;Scott Meyers作为资源非常棒。


6

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"));

4

您可以将其make_tuple用于包扩展,因为它会引入一个上下文,其中,扩展产生的序列是有效的

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

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

演示版


这不能保证bar调用哪个命令
Eric

1

根据此处的答案,这是一个完整的示例。

复制示例,console.log如JavaScript中所示:

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;
    }
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.