为啥 C++11 类内初始化程序不能使用括号?
Posted
技术标签:
【中文标题】为啥 C++11 类内初始化程序不能使用括号?【英文标题】:Why C++11 in-class initializer cannot use parentheses?为什么 C++11 类内初始化程序不能使用括号? 【发布时间】:2021-12-19 01:42:11 【问题描述】:例如,我不能这样写:
class A
vector<int> v(12, 1);
;
我只能这样写:
class A
vector<int> v1 12, 1 ;
vector<int> v2 = vector<int>(12, 1);
;
C++11语言设计的差异有哪些考虑?
【问题讨论】:
前者调用vector在相关的proposal 非静态数据成员初始化器中明确提到了这种选择背后的理由:
在 Kona 中提出的关于标识符范围的问题:
在 07 年 9 月的 Kona 会议上,核心工作组讨论期间,出现了一个关于初始化程序中标识符范围的问题。我们是否希望允许类范围具有向前查找的可能性?还是我们想要求初始化器在解析时定义良好?
需要什么:
类范围查找的动机是我们希望能够将任何东西放入非静态数据成员的初始化程序中,我们可以将其放入内存初始化程序中而不会显着改变语义(模直接初始化 vs. 直接初始化)复制初始化):
int x();
struct S
int i;
S() : i(x()) // currently well-formed, uses S::x()
// ...
static int x();
;
struct T
int i = x(); // should use T::x(), ::x() would be a surprise
// ...
static int x();
;
问题一:
不幸的是,这使得“(表达式列表)”形式的初始值设定项在解析声明时变得模棱两可:
struct S
int i(x); // data member with initializer
// ...
static int x;
;
struct T
int i(x); // member function declaration
// ...
typedef int x;
;
一种可能的解决方案是依赖于现有规则,即如果声明可以是对象或函数,那么它就是函数:
struct S
int i(j); // ill-formed...parsed as a member function,
// type j looked up but not found
// ...
static int j;
;
类似的解决方案是应用另一个现有规则,目前仅在模板中使用,如果 T 可以是类型或其他东西,那么它就是其他东西;如果我们真的指的是类型,我们可以使用“typename”:
struct S
int i(x); // unabmiguously a data member
int j(typename y); // unabmiguously a member function
;
这两种解决方案都引入了许多用户可能会误解的微妙之处(comp.lang.c++ 上关于为什么“int i();”在块范围内没有声明默认值的许多问题证明了这一点 -初始化 int)。
本文提出的解决方案是只允许“= initializer-clause”和“ initializer-list ”形式的初始化器。这解决了大多数情况下的歧义问题,例如:
HashingFunction hash_algorithm"MD5";
在这里,我们不能使用 = 形式,因为 HasningFunction 的构造函数是显式的。 在特别棘手的情况下,一个类型可能不得不被提及两次。考虑:
vector<int> x = 3; // error: the constructor taking an int is explicit
vector<int> x(3); // three elements default-initialized
vector<int> x3; // one element with the value 3
在这种情况下,我们必须使用适当的符号在两个备选方案之间进行选择:
vector<int> x = vector<int>(3); // rather than vector<int> x(3);
vector<int> x3; // one element with the value 3
问题 2:
另一个问题是,由于我们建议不对初始化静态数据成员的规则进行更改,添加 static 关键字可能会使格式良好的初始化器格式不正确:
struct S
const int i = f(); // well-formed with forward lookup
static const int j = f(); // always ill-formed for statics
// ...
constexpr static int f() return 0;
;
问题 3:
第三个问题是类范围查找可能会将编译时错误转变为运行时错误:
struct S
int i = j; // ill-formed without forward lookup, undefined behavior with
int j = 3;
;
(除非被编译器捕获,否则 i 可能会被初始化为 j 的未定义值。)
提案:
CWG 在 Kona 进行了一次 6 比 3 的投票,以支持类范围查找;这就是本文的建议,非静态数据成员的初始化器仅限于“= initializer-clause”和“ initializer-list ”形式。
我们相信:
问题 1:这个问题不会发生,因为我们没有提出 () 表示法。 = 和 初始化符号不会遇到这个问题。
问题 2:添加 static 关键字会产生许多差异,这是其中最小的一个。
问题 3:这不是一个新问题,而是与构造函数初始化程序已经存在的初始化顺序问题相同。
【讨论】:
+1 用于挖掘和格式化 SO。【参考方案2】:一个可能的原因是允许使用括号会立即将我们带回most vexing parse。考虑以下两种类型:
struct foo ;
struct bar
bar(foo const&)
;
现在,您有一个要初始化的bar
类型的数据成员,因此您将其定义为
struct A
bar B(foo());
;
但是您在上面所做的是声明一个名为 B
的函数,它按值返回一个 bar
对象,并接受一个具有签名 foo()
的函数的单个参数(返回一个 foo
并且不'不接受任何论据)。
从 *** 上针对此问题提出的问题的数量和频率来看,这是大多数 C++ 程序员发现的令人惊讶和不直观的问题。添加新的 brace-or-equal-initializer 语法是避免这种歧义并从头开始的机会,这可能是 C++ 委员会选择这样做的原因。
bar Bfoo;
bar B = foo();
如预期的那样,上面两行都声明了一个名为 B
类型为 bar
的对象。
除了上面的猜测之外,我想指出您在上面的示例中做了两件截然不同的事情。
vector<int> v1 12, 1 ;
vector<int> v2 = vector<int>(12, 1);
第一行将v1
初始化为一个包含两个元素12
和1
的向量。第二个创建一个向量v2
,其中包含12
元素,每个元素都初始化为1
。
请注意这条规则 - 如果一个类型定义了一个采用initializer_list<T>
的构造函数,那么当该类型的初始值设定项是 braced-init 时,该构造函数总是首先被考虑-列表。仅当采用 initializer_list
的构造函数不可行时,才会考虑其他构造函数。
【讨论】:
在参数声明中使用时,foo()
是一个函数指针,而不是函数本身,就像内置数组声明一样。
@Lingxi 这不是我说的吗?
我认为逻辑不能可靠地指导 C++ 的小细节。例如,从逻辑上讲,既然列表初始化可以写成v112, 1
,那么可以选择v112,1
的含义来支持普通的构造函数调用。这将是我作为设计师的选择,从这里的“白板”开始。 ;-)
@Praetorian 在您的原始陈述中,这听起来有点像对我的功能的引用。问题不大,真的。
这比其他地方出现的最令人头疼的解析还差多少?以上是关于为啥 C++11 类内初始化程序不能使用括号?的主要内容,如果未能解决你的问题,请参考以下文章
c++11:为啥静态 constexpr 的类内初始化不是定义?