当大括号初始值设定项列表中是否有序列点应用于构造函数时
Are there sequence points in braced initializer lists when they apply to constructors?
根据 n4296 C++标准文档:
[dcl.init.list] (8.5.4.4) (pg223-224)
在大括号初始化列表的初始值设定项列表中, 初始值设定项子句,包括由包扩展产生的任何子句 (14.5.3),按其出现的顺序进行评估。那是 与给定相关的每个值计算和副作用 初始值设定项子句在每个值计算之前进行排序,并且 与它后面的任何初始值设定项子句相关的副作用 初始值设定项列表的逗号分隔列表。[注:本 无论 初始化;例如,当 初始值设定项列表被解释为构造函数调用的参数, 即使通常没有排序约束 调用的参数。—尾注 ]
(强调我的)
注释添加在此处: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';
}
应打印"10"。
这是我的推理:
- MyType 正在通过大括号初始化列表进行初始化 按
- 顺序计算大括号初始化列表
- 即使它被解释为构造函数调用的参数
- 这意味着它应该被评估为 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';
}
但事实并非如此。第一个示例打印"16",第二个示例打印"10"
从字面上看,我能得到的每个供应商的每个编译器都打印了"16",似乎忽略了标准的那部分,也没有插入序号。
我在这里错过了什么?
注意:以下似乎与此问题有关:
- (优化?关于 GCC 标准::线程的错误
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51253
答案似乎是肯定的,这是 GCC 和 MSVC 中的一个错误。
这是此问题的状态:
- 关于初始化列表规则,有几个针对 GCC 的错误。他们中的大多数完全没有被海湾合作委员会团队承认。这至少意味着 G++ 在这里确实存在错误,因为问题尚未因无效而关闭
- 我收到了MSVC编译器团队的非官方消息,这实际上是他们编译器中的一个错误,他们正在内部修复它。但是,我没有外部错误可以指出。从 MSVC 2015 更新 3 开始,旧行为仍然存在。
- Clang,在这一点上是迄今为止最迂腐的标准抱怨编译器,它以标准似乎阅读的方式实现它。
我个人的调查,在会议上与C++专家的讨论,以及我从编译器开发人员那里收到的非官方答案表明,这是MSVC和GCC中的一个错误,但我总是不愿意回答我自己关于StackOverflow的问题。但我们来了。
本说明与求值顺序无关。正如在其中一条评论中所述,这是关于将实际参数转换为右值的顺序,标准没有定义这种顺序。您应该会收到以下警告 (gcc):
17:58: warning: operation on 'i' may be undefined [-Wsequence-point]
我稍微修改了一个程序,以演示参数的评估如何使用 {} 和 ()。
通过这样的修改,程序不依赖于将左值转换为右值的顺序,因此不会有让您失望的歧义。
#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;
}
以及这个程序对 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
海湾合作委员会:
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
如您所见,在大括号的情况下,子句在两个编译器下按出现顺序计算,这与你提供的注释相对应。
- 无论如何,我可以确定构造函数是否存在吗?
- 使用 CTRP 时,是否访问访问父构造函数 UB 中的子属性?
- C++矢量复制构造函数和赋值运算符是否也复制保留空间?
- C++方法是否可以根据传递给构造函数的参数具有不同的返回类型?
- 如何检测除整数以外的任何内容是否传递给我的类构造函数?
- 你能在类和构造函数中初始化吗,这是否正确?
- 在对象构造期间,将指向尚未构造的子对象的指针传递给另一个子对象的构造函数是否危险?
- 在 constexpr 构造函数中初始化数组是否合法?
- 如果这不是类的"复制构造函数",是否可以移动对象?
- 为什么复制构造函数不需要检查输入对象是否指向自身?
- std::shared_ptr 使用别名构造函数,是否可以检索初始指针值?
- 构造函数是否有一种现代C++方法来了解其'container'类?
- 如果我也使用复制构造函数并且重载 = 运算符,我是否需要析构函数?
- 我是否需要在构造函数中显式初始化 std::unique_ptr?
- 是否可以避免在以下代码中复制/移动构造函数的需要?
- 使用 lambda 作为构造函数参数是否需要C++ 17?
- C++,是否有可能/如何定义在.h和.cpp源文件中调用函数的类构造函数
- 移动构造函数是否C++过时?
- 如何构造一个类型特征,可以判断一个类型的私有方法是否可以在另一个类型的构造函数中调用?
- 在 c++ 中将对象设置为等于同一类的构造函数是否有效?