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

时间:2016-12-12 16:22:11

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

给出以下辅助函数

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案例中,我们正在“折叠”test3(Is, args...)表达Is。但我们必须写test3(Is..., args...)而不是test3(Is, args...)...

使用标准的这种奇怪逻辑,你也可以写f(g(args...))而不是f(g(args)...) - 但这是无效的。看起来语言在不同的上下文中使用不同的逻辑。

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

2 个答案:

答案 0 :(得分:7)

  

test3案例中我们是&#34;折叠&#34;关于test3(Is, args...)的表达式Is。但我们必须写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);

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))。有时你需要前者,有时你需要后者。拥有允许两者的语法非常重要。

答案 1 :(得分:0)

以下是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()返回void,因此我们使用逗号运算符:(<CALL>, void(), 0)void()用于防止逗号运算符重载,它被添加为详尽的,在您的示例中不需要。

最后,虚拟初始值设定项列表必须存储在某处,因此int数组是一个很好的占位符。

然而,当你写:

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

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