为什么 C++11 不支持指定初始值设定项列表作为 C99
Why does C++11 not support designated initializer lists as C99?
考虑:
struct Person
{
int height;
int weight;
int age;
};
int main()
{
Person p { .age = 18 };
}
上面的代码在 C99 中是合法的,但在 C++11 中是不合法的。
c++11 标准委员会排除对这样一个方便功能的支持的理由是什么?
17年7月15日,P0329R4被接受为c++20标准:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
这带来了对 c99 的指定初始值设定项的有限支持。C.1.7[diff.decl].4 对此限制描述如下:
struct A { int x, y; };
struct B { struct A a; };
以下指定初始化在 C 中有效,在 C++ 中受到限制:
-
struct A a = { .y = 1, .x = 2 }
在C++中无效,因为指示符必须出现在数据成员的声明顺序中 -
int arr[3] = { [1] = 5 }
在C++中无效,因为不支持数组指定的初始化 -
struct B b = {.a.x = 0}
在C++中无效,因为指示符无法嵌套 -
struct A c = {.x = 1, 2}
在C++中无效,因为所有数据成员或没有数据成员必须由指示符初始化
对于 c++17 及更早版本,Boost 实际上支持指定初始化器,并且有许多建议添加对 c++ 标准的支持,例如:n4172 和 Daryle Walker 的向初始值设定项添加指定项的提案。这些提案引用了 Visual C++、gcc 和 Clang 中 c99 指定初始值设定项的实现,声称:
我们相信这些变化将相对容易实施
但标准委员会一再拒绝此类提案,称:
EWG发现了所提出的方法存在各种问题,并且认为尝试解决问题是不可行的,因为它已经尝试了很多次,每次都失败了。
Ben Voigt的评论帮助我看到了这种方法无法克服的问题;鉴于:
struct X {
int c;
char a;
float b;
};
这些函数在c99中调用的顺序是什么:struct X foo = {.a = (char)f(), .b = g(), .c = h()}
?令人惊讶的是,在c99中:
任何初始值设定项中子表达式的求值顺序都是不确定排序的 [1]
(Visual C++、gcc 和 Clang 似乎有一个商定的行为,因为他们都会按这个顺序进行调用:)
-
h()
-
f()
-
g()
但是标准的不确定性质意味着,如果这些函数有任何交互,则生成的程序状态也将是不确定的,并且编译器不会警告您: 有没有办法收到有关行为异常的指定初始值设定项的警告?
C++ 确实有严格的初始值设定项列表要求 11.6.4[dcl.init.list]4:
在大括号初始化列表的初始值设定项列表中,初始值设定项子句(包括由包扩展 (17.5.3) 产生的任何子句)将按其出现的顺序进行评估。也就是说,与给定初始值设定项子句关联的每个值计算和副作用在初始值设定项列表的逗号分隔列表中与它后面的任何初始值设定项子句关联的每个值计算和副作用之前进行排序。
因此,c ++支持需要按以下顺序执行:
-
f()
-
g()
-
h()
破坏与以前的 c99 实现的兼容性。
如上所述,此问题已通过 c++20 中接受的指定初始值设定项的限制而规避。它们提供标准化的行为,保证指定初始值设定项的执行顺序。
有点黑客行为,所以只是为了好玩而分享。
#define with(T, ...)
([&]{ T ${}; __VA_ARGS__; return $; }())
并像这样使用它:
MyFunction(with(Params,
$.Name = "Foo Bar",
$.Age = 18
));
扩展到:
MyFunction(([&] {
Params ${};
$.Name = "Foo Bar", $.Age = 18;
return $;
}()));
C++有构造函数。如果只初始化一个成员是有意义的,那么可以通过实现适当的构造函数在程序中表达。这是C++提倡的那种抽象。
另一方面,指定的初始值设定项功能更多地是关于公开成员并使其易于直接在客户端代码中访问。这导致一个人年龄在18岁(岁?),但身高和体重为零。
换句话说,指定的初始值设定项支持公开内部的编程风格,并且客户端可以灵活地决定如何使用该类型。
C++更感兴趣的是将灵活性放在类型设计器的一侧,因此设计人员可以轻松正确使用类型,而难以正确使用类型。让设计器控制如何初始化类型是其中的一部分:设计器确定构造函数、类内初始值设定项等。
指定的初始值设定项目前包含在 C++20 的工作主体中:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf,所以我们最终可能会看到它们!
C++11 Lack 的两个核心 C99 功能提到了"指定的初始值设定项和C++"。
我认为"指定的初始值设定项"与潜在的优化有关。这里我用"gcc/g++"5.1 作为例子。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
int x;
int y;
};
const struct point a_point = {.x = 0, .y = 0};
int foo() {
if(a_point.x == 0){
printf("x == 0");
return 0;
}else{
printf("x == 1");
return 1;
}
}
int main(int argc, char *argv[])
{
return foo();
}
我们在编译时就知道,a_point.x
为零,因此我们可以预期foo
被优化为单个printf
。
$ gcc -O3 a.c
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function foo:
0x00000000004004f0 <+0>: sub $0x8,%rsp
0x00000000004004f4 <+4>: mov $0x4005bc,%edi
0x00000000004004f9 <+9>: xor %eax,%eax
0x00000000004004fb <+11>: callq 0x4003a0 <printf@plt>
0x0000000000400500 <+16>: xor %eax,%eax
0x0000000000400502 <+18>: add $0x8,%rsp
0x0000000000400506 <+22>: retq
End of assembler dump.
(gdb) x /s 0x4005bc
0x4005bc: "x == 0"
foo
经过优化,仅打印x == 0
。
对于C++版本,
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
point(int _x,int _y):x(_x),y(_y){}
int x;
int y;
};
const struct point a_point(0,0);
int foo() {
if(a_point.x == 0){
printf("x == 0");
return 0;
}else{
printf("x == 1");
return 1;
}
}
int main(int argc, char *argv[])
{
return foo();
}
这是优化汇编代码的输出。
g++ -O3 a.cc
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function _Z3foov:
0x00000000004005c0 <+0>: push %rbx
0x00000000004005c1 <+1>: mov 0x200489(%rip),%ebx # 0x600a50 <_ZL7a_point>
0x00000000004005c7 <+7>: test %ebx,%ebx
0x00000000004005c9 <+9>: je 0x4005e0 <_Z3foov+32>
0x00000000004005cb <+11>: mov $0x1,%ebx
0x00000000004005d0 <+16>: mov $0x4006a3,%edi
0x00000000004005d5 <+21>: xor %eax,%eax
0x00000000004005d7 <+23>: callq 0x400460 <printf@plt>
0x00000000004005dc <+28>: mov %ebx,%eax
0x00000000004005de <+30>: pop %rbx
0x00000000004005df <+31>: retq
0x00000000004005e0 <+32>: mov $0x40069c,%edi
0x00000000004005e5 <+37>: xor %eax,%eax
0x00000000004005e7 <+39>: callq 0x400460 <printf@plt>
0x00000000004005ec <+44>: mov %ebx,%eax
0x00000000004005ee <+46>: pop %rbx
0x00000000004005ef <+47>: retq
我们可以看到a_point
并不是真正的编译时常量值。
- Pybind11:将元组列表从Python传递到C++
- 从链接列表c++中删除一个项目
- 如何(从固定列表中)选择一个数字序列,该序列将与目标数字相加
- C++如何通过用户输入删除列表元素
- 读取文件的最后一行并输入到链接列表时出错
- 复制列表初始化的隐式转换的等级是多少
- LNK2038、MSVS2017 MAGMA的原因列表
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 没有为自己的结构调用列表推回方法
- 使用简单类型列表实现的指数编译时间.为什么
- 一对向量构造函数:初始值设定项列表与显式构造
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 通过for循环使用用户输入填充列表
- C++:如何使函数只返回作为列表一部分的字符串
- 概念中的cv限定符需要表达式参数列表
- 下面是我为检测链接列表中的循环而制作的代码
- 建议在运行时将带有类实例的列表从c++导入qml
- 如何维护资源管理器项目视图中当前可见的项目列表
- C99 中参数列表为空的函数与 C++98 不兼容
- 为什么 C++11 不支持指定初始值设定项列表作为 C99