【问题标题】:C++11 initializer lists on variadic template's parameters: why isn't this working可变参数模板参数上的 C++11 初始值设定项列表:为什么这不起作用
【发布时间】:2013-04-07 17:17:00
【问题描述】:

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

#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;
}

怎么会?

【问题讨论】:

  • 编译器错误?从您提供的链接中,从 gcc 切换到 clang 或 Intel 将产生您期望的结果。
  • 你说得对,Drew,clang 做得对
  • (或未定义的行为...)
  • 我可能错了,但这可能是评估问题的顺序吗?是否有任何东西迫使调用,所以 some_function 以您期望的顺序进行评估?编辑:从 Andy Prowl 那里得到了我的回答。谢谢!
  • VC++ 2013 也有同样的问题。 :-[ 此处报告:connect.microsoft.com/VisualStudio/feedback/details/1075443

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


【解决方案1】:

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

我的建议是明确说明您想要做什么,以及您执行的顺序,并避免滥用逗号运算符(顺便说一句,如果 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 全捕获 lambda 中,然后使用 ... 创建所述 lambda 的一整套实例并传递给 do_in_order,后者通过完美转发按顺序调用它们.

这对于编译器来说应该很容易内联并简化为一系列调用。它直接说明了它的作用,不需要奇怪的 void 强制转换、逗号运算符的使用或值被丢弃的数组。

【讨论】:

    【解决方案2】:

    这似乎是一个错误。输出应该是您所期望的。

    虽然不能保证构造函数调用的参数的求值顺序一般,但可以保证花括号初始化器列表中表达式的求值顺序。

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

    在花括号初始化列表的初始化列表中,初始化子句,包括任何由 pack 产生的子句 扩展 (14.5.3),按照它们出现的顺序进行评估。也就是说,每个值计算和 与给定初始化子句相关的副作用在每个值计算和边之前排序 与在初始化器列表的逗号分隔列表中跟随它的任何初始化器子句相关联的效果。 [注意:无论初始化的语义如何,这种评估顺序都成立;例如,它适用 当初始化列表的元素被解释为构造函数调用的参数时,即使 通常对调用的参数没有顺序限制。 ——尾注]

    【讨论】:

    • 似乎 gcc 只是将其正常的函数调用参数评估顺序应用于作为正常构造函数调用的统一初始化调用。但是,即使调用同一个构造函数,Foo x{a,b};Foo x(a,b); 也不相同,这也有点令人困惑。
    • @ArneMertz:确实,统一初始化方式并没有它想象的那么统一。
    • 我不会这么说的。让聚合初始化器列表从左到右求值,反之非聚合显然不是很统一,不管它们是否必须在 C++03 中用不同的大括号/方括号编写。在我看来,这是一个(现在不可修复的)错误,或者至少是标准中的一个不一致之处,它允许编译器评估函数,尤其是构造函数参数,就像 gcc 那样从右到左进行评估。定义 eval 顺序是确保不同编译器之间可移植性的正确做法,并且在过去用于函数参数评估。
    • @ArneMertz:我认为你把我的话弄错了。我只是同意你的说法,这令人困惑。另一方面,强制编译器总是从左到右评估可能会阻止一些优化。我不是编译器专家,所以我会在这里闭嘴 - 但如果未指定常规函数调用的参数的评估顺序,我希望存在一个很好的理由。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-14
    • 1970-01-01
    • 2017-05-15
    • 1970-01-01
    • 2018-04-27
    • 2017-03-07
    • 2016-11-12
    相关资源
    最近更新 更多