std::forward 参数包的扩展究竟是如何评估的?
How exactly is expansion of a parameter pack evaluated with std::forward?
我想更好地理解 参数包扩展 ,所以我决定进行一些研究,曾经对我来说似乎很明显的东西,在试图理解究竟是什么之后不再那么明显了正在进行。让我们用 std::forward
:
检查标准参数包扩展
template <typename... Ts>
void foo(Ts&& ... ts) {
std::make_tuple(std::forward<Ts>(ts)...);
}
我的理解是,对于任何参数包 Ts
,std::forward<Ts>(ts)...
将导致以逗号分隔的转发参数列表及其相应类型,例如,对于 ts
等于1, 1.0, '1'
,函数体将展开为:
std::make_tuple(std::forward<int&&>(1), std::forward<double&&>(1.0), std::forward<char&&>('1'));
这对我来说很有意义。与函数调用一起使用的参数包扩展会生成以逗号分隔的调用列表,该函数具有适当的参数。
似乎困扰我的是,如果我们想以类似的方式调用一堆函数,为什么有时我们需要引入逗号运算符 (operator,
)?看到this answer,我们可以读到这段代码:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args) {
(bar(args), ...); // <- notice: comma here
}
int main() {
foo2(1, 2, 3, "3");
return 0;
}
后跟将导致以下扩展的信息:
(bar(1), bar(2), bar(3), bar("3"));
公平,有道理,但是... 为什么?为什么这样做,而不是:
template<typename... Args>
static void foo2(Args... args) {
(bar(args)...); // <- notice: no comma here
}
不起作用?根据我的逻辑(“参数包扩展,与函数调用一起使用,会产生一个逗号分隔的调用列表,该函数具有适当的参数”),它应该扩展为:
(bar(1), bar(2), bar(3), bar("3"));
是因为bar()
返回void
吗?那么,将 bar()
更改为:
template<typename T>
static int bar(T t) { return 1; }
没有任何改变。我想它只会扩展为逗号分隔的 1
列表(如果 bar()
是这样设计的,可能会产生一些副作用)。为什么这表现不同?我的逻辑哪里有问题?
My understanding here is that for any parameter pack Ts
, std::forward<Ts>(ts)...
will result in a comma-separated list of forwarded arguments with their corresponding type
好吧,这就是你的问题:它不是这样工作的。或者最后,不完全是。
参数包扩展及其性质由它们的使用位置决定。在 C++17 之前的包扩展只能在某些语法结构中使用,例如花括号初始化列表或函数调用表达式。除了这些构造(前面的列表并不全面)之外,它们的使用是不允许的。 Post-C++17,折叠表达式允许它们跨特定运算符使用。
部分原因在于语法。考虑一下:bar(1, (2, 3), 5)
。这会调用带有 3 个参数的函数;表达式 (2, 3)
解析为单个参数。也就是说,表达式中使用的逗号与函数调用中用作值之间分隔符的逗号是不同的。这种区别是在 语法 级别上进行的;如果我想在一系列函数参数中间调用逗号运算符,我必须将整个东西放在 ()
中,以便编译器将逗号识别为逗号表达式运算符,而不是逗号分隔符。
非折叠包扩展有效地扩展为使用 separation 逗号,而不是表达式逗号。因此,它们只能在逗号分隔类型有效的地方展开。
(bar(args)...)
不起作用的原因是 ()
表达式不能使用第二种逗号。
我想更好地理解 参数包扩展 ,所以我决定进行一些研究,曾经对我来说似乎很明显的东西,在试图理解究竟是什么之后不再那么明显了正在进行。让我们用 std::forward
:
template <typename... Ts>
void foo(Ts&& ... ts) {
std::make_tuple(std::forward<Ts>(ts)...);
}
我的理解是,对于任何参数包 Ts
,std::forward<Ts>(ts)...
将导致以逗号分隔的转发参数列表及其相应类型,例如,对于 ts
等于1, 1.0, '1'
,函数体将展开为:
std::make_tuple(std::forward<int&&>(1), std::forward<double&&>(1.0), std::forward<char&&>('1'));
这对我来说很有意义。与函数调用一起使用的参数包扩展会生成以逗号分隔的调用列表,该函数具有适当的参数。
似乎困扰我的是,如果我们想以类似的方式调用一堆函数,为什么有时我们需要引入逗号运算符 (operator,
)?看到this answer,我们可以读到这段代码:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args) {
(bar(args), ...); // <- notice: comma here
}
int main() {
foo2(1, 2, 3, "3");
return 0;
}
后跟将导致以下扩展的信息:
(bar(1), bar(2), bar(3), bar("3"));
公平,有道理,但是... 为什么?为什么这样做,而不是:
template<typename... Args>
static void foo2(Args... args) {
(bar(args)...); // <- notice: no comma here
}
不起作用?根据我的逻辑(“参数包扩展,与函数调用一起使用,会产生一个逗号分隔的调用列表,该函数具有适当的参数”),它应该扩展为:
(bar(1), bar(2), bar(3), bar("3"));
是因为bar()
返回void
吗?那么,将 bar()
更改为:
template<typename T>
static int bar(T t) { return 1; }
没有任何改变。我想它只会扩展为逗号分隔的 1
列表(如果 bar()
是这样设计的,可能会产生一些副作用)。为什么这表现不同?我的逻辑哪里有问题?
My understanding here is that for any parameter pack
Ts
,std::forward<Ts>(ts)...
will result in a comma-separated list of forwarded arguments with their corresponding type
好吧,这就是你的问题:它不是这样工作的。或者最后,不完全是。
参数包扩展及其性质由它们的使用位置决定。在 C++17 之前的包扩展只能在某些语法结构中使用,例如花括号初始化列表或函数调用表达式。除了这些构造(前面的列表并不全面)之外,它们的使用是不允许的。 Post-C++17,折叠表达式允许它们跨特定运算符使用。
部分原因在于语法。考虑一下:bar(1, (2, 3), 5)
。这会调用带有 3 个参数的函数;表达式 (2, 3)
解析为单个参数。也就是说,表达式中使用的逗号与函数调用中用作值之间分隔符的逗号是不同的。这种区别是在 语法 级别上进行的;如果我想在一系列函数参数中间调用逗号运算符,我必须将整个东西放在 ()
中,以便编译器将逗号识别为逗号表达式运算符,而不是逗号分隔符。
非折叠包扩展有效地扩展为使用 separation 逗号,而不是表达式逗号。因此,它们只能在逗号分隔类型有效的地方展开。
(bar(args)...)
不起作用的原因是 ()
表达式不能使用第二种逗号。