问题 C ++ 11初始化器列出了可变参数模板的参数:为什么这不起作用


将可变参数模板的参数包含在初始化程序列表中应该确保它们按顺序进行评估,但不会在此处进行:

#include <iostream>
using namespace std;


template<class T> void some_function(T var)
{
   cout << var << endl;
}

struct expand_aux {
    template<typename... Args> expand_aux(Args&&...) { }
};

template<typename... Args> inline void expand(Args&&... args) 
{
   bool b[] = {(some_function(std::forward<Args>(args)),true)...}; // This output is 42, "true", false and is correct
   cout << "other output" << endl;
   expand_aux  temp3 { (some_function(std::forward<Args>(args)),true)...  }; // This output isn't correct, it is false, "true", 42
}

int main()
{
   expand(42, "true", false);

   return 0;
}

怎么来的?


3529
2018-04-15 14:29


起源

编译错误?从您提供的链接,从gcc切换到clang或Intel将产生您期望的结果。 - Drew Dormann
你是对的Drew,clang做得对 - Paul
(或未定义的行为......) - Drew Dormann
我可能错了,但这可能是评估问题的一个顺序吗?是否有任何强制调用,所以some_function按您期望的顺序进行评估?编辑:得到了Andy Prowl的回答。谢谢 ! - François Moisan
VC ++ 2013有完全相同的问题。 : - [报告在这里: connect.microsoft.com/VisualStudio/feedback/details/1075443 - ildjarn


答案:


这似乎是一个 窃听器。输出应该是您期望的输出。

虽然不能保证构造函数调用的参数的评估顺序 一般来说,对支撑初始化列表中表达式的评估顺序有保证。

根据C ++ 11标准的第8.5.4 / 4段:

在braced-init-list的initializer-list中,initializer-clause,包括pack中的任何结果   扩展(14.5.3),按它们出现的顺序进行评估。也就是说,每个值计算和   在每个值计算和侧面之前,对与给定初始化子句相关联的副作用进行排序   与在初始化列表的逗号分隔列表中跟随它的任何initializer子句相关联的效果。   [注意:无论初始化的语义如何,此评估顺序都保持不变;例如, 它适用   当initializer-list的元素被解释为构造函数调用的参数时,即使如此   通常,对呼叫的参数没有排序限制。 - 尾注]


12
2018-04-15 14:39



似乎gcc只是将它的正常函数调用参数评估顺序应用于统一初始化调用,这是正常的构造函数调用。但是,这也有点令人困惑 Foo x{a,b};和 Foo x(a,b); 即使调用相同的构造函数也不一样。 - Arne Mertz
@ArneMertz:的确,统一初始化是 办法 不像它假装的那样统一。 - Andy Prowl
我不会这么说。具有从左到右评估的聚合初始化列表并且反过来非聚合显然不是非常统一,无论它们是否必须在C ++ 03中使用不同的大括号/括号进行编写。 Imo它是一个(现在是不可修复的)错误,或者至少是标准中的一个不一致,它允许编译器评估函数,尤其是构造函数参数,从而像gcc那样从右到左进行求值。定义eval命令是确保不同编译器之间可移植性的正确方法,并且在过去一直用于函数参数评估。 - Arne Mertz
@ArneMertz:我觉得你的判决错了。我只是同意你的陈述,这令人困惑。另一方面,强制编译器 总是 从左到右评估可能会阻止一些优化。我不是编译器专家,所以我会在这里闭嘴 - 但如果未指定常规函数调用的参数的评估顺序,我希望存在一个很好的理由。 - Andy Prowl


如上所述,您的问题是编译器错误。您编写的代码应按顺序评估其参数。

我的建议是明确你想做什么,你做了什么顺序,并避免滥用逗号运算符(顺便说一下,如果你的代码可能会表现得很奇怪,如果 some_function 返回一个覆盖的类型 operator,)或使用初始化列表保证(虽然标准,但也相对模糊)。

我的解决方案是编写然后使用 do_in_order

// do nothing in order means do nothing:
void do_in_order() {}
// do the first passed in nullary object, then the rest, in order:
template<typename F0, typename... Fs>
void do_in_order(F0&& f0, Fs&&... fs) {
  std::forward<F0>(f0)();
  do_in_order( std::forward<Fs>(fs)... );
}

您使用这样的:

do_in_order( [&]{ some_function(std::forward<Args>(args)); }... );

你在匿名的nullary full-capture lambda中包装你想要做的动作,然后使用 ... 创建一整套所述lambda的实例并传递给 do_in_order,通过完美转发按顺序调用它们。

这对于编译器来说应该很容易内联并减少到一系列调用。它直接表示它的作用,并不需要奇怪的空转,使用逗号运算符或其值被丢弃的数组。


4
2018-04-15 14:38