繁体   English   中英

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

[英]Rationale of template parameter pack expansion syntax

鉴于以下辅助功能

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

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

1)我们像往常一样扩展模板参数包。

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

2)在这里,扩展...发生在g()函数调用之后。 这也是合理的,因为g()是与每个args调用的:

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

3)用相同的逻辑,我期望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...);
}

我知道,我用,但是,我不明白。 模板扩展的整个概念是表达折叠的一种形式。 不是以C ++ 17的方式,而是以...之前的子表达式相对于可变参数折叠(或根据需要重复)的意义。 test3情况下,我们相对于Is “折叠”表达式test3(Is, args...) 但是我们必须编写test3(Is..., args...)而不是test3(Is, args...)...

有了这种标准的怪异逻辑,您也可以写f(g(args...))代替f(g(args)...) -但这是无效的。 看起来该语言在不同的上下文中使用了不同的逻辑。

不同语法背后的原理是什么?

test3情况下,我们相对于Is “折叠”表达式test3(Is, args...) 但是我们必须编写test3(Is..., args...)而不是test3(Is, args...)....

这实际上是不正确的。 test3(Is..., args...)将展开Is到位,然后args到位。 因此,调用test3(index_sequence<0,1,2>, x, y, z)最终将调用test3(0, 1, 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);

C ++ 17调用此方法的方式是:

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

这实际上不是一个不同的语法。 你必须要拓展不同的双参数包: args函数调用中和Is它周围,这样就意味着你有两个...秒。 逗号只是表明它们是单独的语句的一种方式。

自由放置...意味着您可以根据需要折叠它:

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

有了这种标准的怪异逻辑,您也可以写f(g(args...))代替f(g(args)...) -但这是无效的

那不是奇怪的逻辑。 那意味着不同的事情。 第一个扩展为f(g(a0, a1, a2, ..., aN)) ,第二个扩展为f(g(a0), g(a1), g(a2), ..., g(aN)) 有时您需要前者,有时则需要后者。 具有同时允许两者的语法非常重要。

这是您的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)... };      
}

参数包扩展可以在特定的上下文中进行(通常是函数/模板参数/参数列表和大括号初始化列表)。 像您一样突然进行扩展是非法的。 为了避免这种情况,我们需要使其在合法的上下文中发生,这里是一个初始化列表。 但是,我们必须确保所述初始化列表没有格式错误:

  • 它不能为空:因此我们在开始时输入0
  • 必须输入test3_impl()的类型: test3_impl()返回void,因此我们使用逗号运算符: (<CALL>, void(), 0) void()此处用于防止逗号运算符重载,它被添加为详尽无遗,在您的示例中不是必需的。

最后,该虚拟初始化程序列表必须存储在某个位置,因此int数组是一个很好的占位符。

但是,当您编写时:

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

这实际上调用f(IsTail..., args...)而不是f(args...)

暂无
暂无

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

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