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

Rationale of template parameter pack expansion syntax

本文关键字:语法 理由 扩展 参数 包扩展      更新时间:2023-10-16

给定以下辅助功能

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()的功能调用之后。这也是合理的,因为每个args都被调用g()

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方式,而是从...之前的子表达(如果愿意)折叠(或重复),相对于variadic参数。在test3的情况下,我们将test3(Is, args...)的表达式CC_9"折叠"。但是我们必须编写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);

C 17调用的方法是:

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

这并不是真正不同的语法。您有两个参数包,要以不同的方式扩展:args在函数调用中和围绕它的Is中,这意味着您有两个... s。逗号只是一种表明这些单独语句的方式。

...放置中的自由意味着您无论如何都可以折叠它:

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

参数包扩展可以在特定上下文中进行(通常是功能/模板参数/参数列表和Brace-Initializer列表)。像您所做的那样,蓝色的扩展是非法的。为了避免这种情况,我们需要在法律环境中进行此初始化列表。但是,我们必须确保上所述初始化列表并非不明显:

  • 它一定不能为空:所以我们在开始时投入0
  • 它必须经过良好的效果:test3_impl()返回void,因此我们使用逗号操作员:(<CALL>, void(), 0)void()在这里可以防止逗号操作员过载,添加为详尽,在您的示例中不需要。

最后,该虚拟初始化器列表必须存储在某个地方,因此INT数组是一个好的占位符。

但是,当您写作时:

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

这实际上称为f(IsTail..., args...),而不是f(args...)