简体   繁体   English

模板参数包扩展语法的原理

[英]Rationale of template parameter pack expansion syntax

Given the following helper functions 鉴于以下辅助功能

template <typename ... Ts>
auto f(Ts&& ... args) {}

template <typename T>
auto g(T x) { return x; }

1) We expand a template parameter pack as usual. 1)我们像往常一样扩展模板参数包。

template <typename ... Ts>
void test1(Ts&& ... args)
{
    f(args...);
}

2) Here the expansion ... occurs after the function call of g() . 2)在这里,扩展...发生在g()函数调用之后。 This is also reasonable, since g() is being called with each args : 这也是合理的,因为g()是与每个args调用的:

template <typename ... Ts>
void test2(Ts&& ... args)
{
    f(g(args)...);
}

3) With the same logic I would expect test3(Is, args...)... , but no. 3)用相同的逻辑,我期望test3(Is, args...)... ,但是没有。 You have to write test3(Is..., args...) : 您必须编写test3(Is..., args...)

template <typename ... Ts>
void test3(size_t i, Ts&& ... args)
{
    f(args...);
}

template <typename ... Ts>
void test3(std::index_sequence<Is...>, Ts&& ... args)
{
    // would expect test3(Is, args...)...;
    test3(Is..., args...);
}

I know it, I use it, but well, I don't get it. 我知道,我用,但是,我不明白。 The whole concept of template expansion is a form of expression folding. 模板扩展的整个概念是表达折叠的一种形式。 Not in the C++17 way, but in the sense that the subexpression before the ... is being folded (or repeated if you like) in respect to the variadic parameter. 不是以C ++ 17的方式,而是以...之前的子表达式相对于可变参数折叠(或根据需要重复)的意义。 In the test3 case we are "folding" the expression test3(Is, args...) in respect to Is . test3情况下,我们相对于Is “折叠”表达式test3(Is, args...) Yet we have to write test3(Is..., args...) instead of test3(Is, args...)... . 但是我们必须编写test3(Is..., args...)而不是test3(Is, args...)...

With this weird logic of the standard you could also write f(g(args...)) instead of f(g(args)...) - however that's invalid. 有了这种标准的怪异逻辑,您也可以写f(g(args...))代替f(g(args)...) -但这是无效的。 It looks like the language uses different logic in different contexts. 看起来该语言在不同的上下文中使用了不同的逻辑。

What is the rationale behind the different syntax? 不同语法背后的原理是什么?

In the test3 case we are "folding" the expression test3(Is, args...) in respect to Is . test3情况下,我们相对于Is “折叠”表达式test3(Is, args...) Yet we have to write test3(Is..., args...) instead of test3(Is, args...).... 但是我们必须编写test3(Is..., args...)而不是test3(Is, args...)....

That's actually incorrect. 这实际上是不正确的。 test3(Is..., args...) will expand Is in place and then args in place. test3(Is..., args...)将展开Is到位,然后args到位。 So the call test3(index_sequence<0,1,2>, x, y, z) would end up calling test3(0, 1, 2, x, y, z) , which isn't what you want to happen. 因此,调用test3(index_sequence<0,1,2>, x, y, z)最终将调用test3(0, 1, 2, x, y, z) ,这不是您想要发生的事情。 You want test3(0, x, y, z); test3(1, x, y, z); test3(2, x, y, z); 您需要test3(0, x, y, z); test3(1, x, y, z); test3(2, x, y, z); test3(0, x, y, z); test3(1, x, y, z); test3(2, x, y, z); .

The C++17 way of invoking this would be: C ++ 17调用此方法的方式是:

(test3(Is, args...), ...);

This isn't really a different syntax. 这实际上不是一个不同的语法。 You have two parameter packs that you want to expand differently: args within the function call and Is around it, so that means you have two ... s. 你必须要拓展不同的双参数包: args函数调用中和Is它周围,这样就意味着你有两个...秒。 The comma is just a way to indicate that these are separate statements. 逗号只是表明它们是单独的语句的一种方式。

The freedom in ... placement means you can fold it anyway you need: 自由放置...意味着您可以根据需要折叠它:

(test3(Is, args), ...);    // test3(0,x); test3(1,y); test3(2,z);
(test3(Is..., args), ...); // test3(0,1,2,x); test3(0,1,2,y); test3(0,1,2,z);
test3(Is..., args...);     // test3(0,1,2,x,y,z);

With this weird logic of the standard you could also write f(g(args...)) instead of f(g(args)...) - however that's invalid 有了这种标准的怪异逻辑,您也可以写f(g(args...))代替f(g(args)...) -但这是无效的

That's not weird logic. 那不是奇怪的逻辑。 Those mean different things. 那意味着不同的事情。 The first expands to f(g(a0, a1, a2, ..., aN)) and the second expands to f(g(a0), g(a1), g(a2), ..., g(aN)) . 第一个扩展为f(g(a0, a1, a2, ..., aN)) ,第二个扩展为f(g(a0), g(a1), g(a2), ..., g(aN)) Sometimes you need the former and sometimes you need the latter. 有时您需要前者,有时则需要后者。 Having syntax that allows for both is pretty important. 具有同时允许两者的语法非常重要。

Here is what your test3 should look like: 这是您的test3样子:

template <typename ... Ts>
void test3_impl(size_t i, Ts&&... args) {
    f(std::forward<Ts>(args)...);
}

template <size_t ... Is, typename ... Ts>
void test3(std::index_sequence<Is...>, Ts&&... args) {
    int dummy[] = { 0, (test3_impl(Is, args...), void(), 0)... };      
}

Parameter pack expansion can take place in specific contexts (usually function/template arguments/parameters lists and brace-initializer list). 参数包扩展可以在特定的上下文中进行(通常是函数/模板参数/参数列表和大括号初始化列表)。 An expansion out of the blue like you did is illegal. 像您一样突然进行扩展是非法的。 To circumvent this, we need to make it happen in a legal context, here an initializer list. 为了避免这种情况,我们需要使其在合法的上下文中发生,这里是一个初始化列表。 However, we must make sure the said initializer list is not ill-formed: 但是,我们必须确保所述初始化列表没有格式错误:

  • It must not be empty: so we throw in a 0 at the beginning 它不能为空:因此我们在开始时输入0
  • It must be well-typed: test3_impl() returns void, so we use the comma operator: (<CALL>, void(), 0) . 必须输入test3_impl()的类型: test3_impl()返回void,因此我们使用逗号运算符: (<CALL>, void(), 0) The void() is here to prevent comma operator overloading, it is added to be exhaustive, it is not required in your example. void()此处用于防止逗号运算符重载,它被添加为详尽无遗,在您的示例中不是必需的。

Finally, that dummy initializer list must be stored somewhere, so an array of int is a good placeholder. 最后,该虚拟初始化程序列表必须存储在某个位置,因此int数组是一个很好的占位符。

However, when you write: 但是,当您编写时:

// Assuming Is... is [I1, IsTail...]
test3_impl(Is..., args...);

This actually calls f(IsTail..., args...) and not f(args...) 这实际上调用f(IsTail..., args...)而不是f(args...)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM