C++ 可变模板包扩展

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25680461/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 11:23:29  来源:igfitidea点击:

Variadic template pack expansion

c++templatesc++11variadic-templates

提问by Viacheslav Dronov

I am trying to learn variadic templates and functions. I can't understand why this code doesn't compile:

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

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

When I compile it fails with the error:

当我编译它失败并出现错误:

Error C3520: 'args': parameter pack must be expanded in this context

错误 C3520:“args”:必须在此上下文中扩展参数包

(in function foo2).

(在函数中foo2)。

回答by T.C.

One of the places where a pack expansion can occur is inside a braced-init-list. You can take advantage of this by putting the expansion inside the initializer list of a dummy array:

可能发生包扩展的地方之一是在支撑初始化列表中。您可以通过将扩展放在虚拟数组的初始化列表中来利用这一点:

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

To explain the content of the initializer in more detail:

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

{ 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.

演示

An important advantage of expanding in {}is that it guarantees left-to-right evaluation.

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



With C++17 fold expressions, you can just write

使用 C++17折叠表达式,您只需编写

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

回答by Angew is no longer proud of SO

Parameter packs can only be expanded in a strictly-defined list of contexts, and operator ,is not one of them. In other words, it's not possible to use pack expansion to generate an expression consisting of a series of subexpressions delimited by operator ,.

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

The rule of thumb is "Expansion can generate a listof ,-separated patterns where ,is a listdelimiter." Operator ,does not construct a list in the grammar sense.

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

To call a function for each argument, you can use recursion (which is the primary tool in the variadic template programmer's box):

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

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

活生生的例子

回答by CoffeeandCode

SHAMELESS COPY [approved by its source]

无耻的复制[经其来源批准]

Parameter packs can only be expanded in a strictly-defined list of contexts, and operator ,is not one of them. In other words, it's not possible to use pack expansion to generate an expression consisting of a series of subexpressions delimited by operator ,.

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

The rule of thumb is "Expansion can generate a list of ,-separated patterns where ,is a list delimiter." Operator ,does not construct a list in the grammar sense.

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

To call a function for each argument, you can use recursion (which is the primary tool in the variadic template programmer's box):

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

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


USEFUL NON-COPIED INFO

有用的非复制信息

Another thing you probably haven't seen in this answer is use of the &&specifier and std::forward. In C++, the &&specifier can mean one of 2 things: rvalue-references, or universal references.

您在此答案中可能还没有看到的另一件事是使用说明&&符 和std::forward。在 C++ 中,说明&&符可以表示两件事之一:右值引用或通用引用。

I won't go into rvalue-references, but to somebody working with variadic templates; universal references are a god-send.

我不会讨论右值引用,而是讨论使用可变参数模板的人;通用参考是天赐之物。

Perfect Forwarding

完美转发

One of the uses of std::forwardand universal references are perfect forwarding of types to other functions.

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

In your example, if we pass an int&to foo2it will be automatically demoted to intbecause of the signature of the generated foo2function after template deduction and if you wanted to then forward this argto another function that would modify it by reference, you will get undesired results (the variable won't be changed) because foo2will be passing a reference to the temporary created by passing an intto it. To get around this, we specify a forwarding function to take anytype of referenceto a variable (rvalue orlvalue). Then, to be sure that we pass the exact type passed in the forwarding function we use std::forward, then and onlythen do we allow the demoting of types; because we are now at the point where it matters most.

在您的示例中,如果我们传递int&foo2它,int由于foo2模板推导后生成的函数的签名,它将自动降级为,如果您想将其转发arg给另一个将通过引用修改它的函数,您将得到不想要的结果(该变量不会更改),因为foo2将传递对通过将 an 传递int给它创建的临时对象的引用。为了解决这个问题,我们指定了一个转发函数来获取对变量(右值左值)的任何类型的引用。然后,为了确保我们传递在我们使用的转发函数中传递的确切类型,然后并且std::forward那么我们是否允许降级类型;因为我们现在正处于最重要的时刻。

If you need to, read more on universal referencesand perfect forwarding; Scott Meyers is pretty great as a resource.

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

回答by Guillaume Racicot

The C++17 solution to this is really close to your expected code:

对此的 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;    
}

This expand the pattern with the comma operator between every expression

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

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

回答by Lorah Attkins

You can use make_tuplefor pack expansion as it introduces a context where the ,sequence produced by an expansion is valid

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

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

Now, I suspect the unused/unnamed/temporary tuple of zeroes that's produced is detectable by the compiler and optimized away.

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

Demo

演示

回答by lama12345

This is a full example, based on the answers here.

这是一个完整的例子,基于这里的答案。

Example to reproduce console.logas seen in JavaScript:

console.log在 JavaScript 中看到的重现示例:

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

Filename e.g. js_console.h:

文件名例如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;
    }
};