当它们应用于构造函数时,在括号初始化列表中是否存在序列点?

lef*_*cus 20 c++ gcc clang visual-c++ c++11

根据n4296 C++标准文档:

[dcl.init.list](8.5.4.4)(第223-224页)

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

(强调我的)

该说明在此处添加:http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1030

这告诉我以下代码:

#include <iostream>

struct MyType {
  MyType(int i, int j, int k, int l)
    : sum(i + j + k + l)
  {

  }

  int sum;
};

int main()
{
  int i = 0;
  std::cout << MyType{ ++i, ++i, ++i, ++i }.sum << '\n';
}
Run Code Online (Sandbox Code Playgroud)

应打印"10".

这是我的理由:

  • MyType正在通过braced-init-list初始化
  • braced-init-lists按顺序进行评估
  • 即使它被解释为构造函数调用的参数
  • 这意味着它应该被评估为MyType(1,2,3,4)

也就是说,上面的代码应该与此代码完全相同:

#include <initializer_list>
#include <iostream>

int main()
{
  int i = 0;
  std::initializer_list<int> il{++i, ++i, ++i, ++i};
  std::cout << *il.begin() + *(il.begin() + 1) + *(il.begin() + 2) + *(il.begin() + 3) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

但事实并非如此.第一个例子打印'16',第二个例子打印'10'

从字面上看,我可以得到的每个供应商的每个编译器打印'16',似乎忽略了标准的那一部分而不插入序列点.

我在这里错过了什么?

注意:以下似乎与此问题有关:

lef*_*cus 6

答案似乎是肯定的,这是 GCC 和 MSVC 中的一个错误。

这是这个问题的状态:

  1. 关于 init-list 规则,有几个针对 GCC 的错误。他们中的大多数人都完全没有得到 GCC 团队的承认。这至少意味着 G++ 在这里确实有错误,因为这些问题没有被关闭为无效
  2. 我从 MSVC 编译器团队收到非正式消息,这实际上是他们编译器中的一个错误,他们正在内部修复它。但是,我没有可以指出的外部错误。从 MSVC 2015 Update 3 开始,旧的行为仍然存在。
  3. Clang 在这一点上是迄今为止最迂腐的符合标准的编译器,它以标准似乎阅读的方式实现它。

我的个人调查、在会议上与 C++ 专家的讨论以及我从编译器开发人员那里收到的非官方答案表明,这是 MSVC 和 GCC 中的一个错误,但我总是不愿意回答我自己关于 StackOverflow 的问题。但我们来了。


小智 0

本注释与评估顺序无关。正如评论之一所述,它是关于将实际参数转换为右值的顺序,而标准没有定义这样的顺序。您应该收到以下警告(gcc):

17:58: warning: operation on 'i' may be undefined [-Wsequence-point]
Run Code Online (Sandbox Code Playgroud)

我稍微修改了一个程序来演示如何使用 {} 和 () 来评估参数。

通过这样的修改,程序不依赖于将左值转换为右值的顺序,因此不会出现令您失望的歧义。

#include <iostream>

struct MyType {
  MyType(int i, int j)
    : sum(i + j)
  {

  }

  int sum;
};

int main()
{
  int i = 0;
  int a,b;
  std::cout << MyType{ (a = ++i), (b = ++i) }.sum << '\n';
  std::cout << "Here clauses are evaluated in order they appear: a=" << a << ", b=" << b << std::endl;
  i = 0;
  std::cout << MyType( (a = ++i), (b = ++i) ).sum << '\n';
  std::cout << "Here order of evaluation depends on implementation: a=" << a << ", b=" << b << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

该程序对于 clang 和 gcc 的输出:

铛:

3
Here clauses are evaluated in order they appear: a=1, b=2
3
Here order of evaluation depends on implementation: a=1, b=2
Run Code Online (Sandbox Code Playgroud)

海湾合作委员会:

3
Here clauses are evaluated in order they appear: a=1, b=2
3
Here order of evaluation depends on implementation: a=2, b=1
Run Code Online (Sandbox Code Playgroud)

如您所见,在使用大括号的情况下,子句将按两个编译器中出现的顺序进行评估,这与您提供的注释相对应。