构造函数干扰成员变量指定初始化器?
Constructor interferes with member variable designated initializer?
一段时间以来,人们已经能够在 GCC 中使用“指定初始化程序”:
struct CC{
double a_;
double b_;
};
CC cc{.a_ = 1., .b_ = 2.}; assert(cc.a_ == 1. and cc.b_ == 2.); // ok
CC cc{.bla = 0., .bli = 0.}; // compile error
但是,当我添加构造函数时,标签会被忽略。
struct CC{
double a_;
double b_;
CC(double a, double b) : a_{a}, b_{b}{}
};
CC cc{.a_ = 1., .b_ = 2.}; assert(cc.a_ == 1. and cc.b_ == 2.); // ok
CC cc{.b_ = 2., .a_ = 1.}; // compiles but labels don't matter only the order, confusing
CC cc{.bla = 2., .bli = 1.}; // compiles but labels don't matter, confusing
换句话说,带有构造函数的初始化语法使标签的行为就像注释一样!,这可能会让人非常困惑,但最重要的是,它很奇怪。
我无意中发现了这个,gcc 8.1 -std=c++2a
。
这是预期的行为吗?
参考:https://en.cppreference.com/w/cpp/language/aggregate_initialization
编辑 2022: 现在编译器支持 -std=c++20
行为是正确的。所有接受 -std=c++20
(10.1 及更高版本)的 GCC 版本也接受上述代码或在应该时给出错误。
编译器不应忽略指定初始值设定项的标签。所有这三个带有构造函数的示例都应该是错误格式的。
由于 C++20 指定初始化程序功能与 GCC 的 C 样式指定初始化程序之间存在冲突,您可能会出现此行为,因为 GCC 只是将它们提供给您,您正在隐式访问它们。如果 GCC 是正确的 C++20 兼容的,一旦你给类型一个构造函数,它就不再是一个聚合,因此指定的初始化器用法将是错误的。
基本上,这是一个由编译器默认为您提供的非 C++ 标准行为引起的驱动程序错误。如果您关闭此功能,很有可能会在这些情况下出现正确的编译器错误。
这是一个 gcc 错误,仍然可以构建 even with -pedantic in which we should receive warnings for any extensions
...to obtain all the diagnostics required by the standard, you should also specify -pedantic ...
和 gcc 声称支持 P0329R4: Designated initializers
对 C++2a
模式的提议 according to the C++ Standards Support in GCC page:
Language Feature | Proposal | Available in GCC?
...
Designated initializers | P0329R4 | 8
为了使用Designated initializers the type should be aggregate [dcl.init.list]p3.1:
If the braced-init-list contains a designated-initializer-list, T shall be an aggregate class. The ordered
identifiers in the designators of the designated-initializer-list shall form a subsequence of the ordered
identifiers in the direct non-static data members of T. Aggregate initialization is performed (11.6.1).
[ Example:
struct A { int x; int y; int z; };
A a{.y = 2, .x = 1}; // error: designator order does not match declaration order
A b{.x = 1, .z = 2}; // OK, b.y initialized to 0
—end example ]
CC
不是根据 [dcl.init.aggr] 的聚合:
An aggregate is an array or a class (Clause 12) with
- (1.1) — no user-provided, explicit, or inherited constructors (15.1),
....
gcc 错误报告
如果我们查看 gcc bug report: Incorrect overload resolution when using designated initializers,我们会在这个给定的示例中看到:
Another test case, reduced from Chromium 70.0.3538.9 and accepted by
clang:
struct S { void *a; int b; };
void f(S);
void g() { f({.b = 1}); }
This fails with
bug.cc: In function ‘void g()’:
bug.cc:3:24: error: could not convert ‘{1}’ from ‘<brace-enclosed initializer list>’ to ‘S’
void g() { f({.b = 1}); }
^
The error suggests the field names are simply ignored entirely during
overload resolution, which also explains the behaviour of the
originally reported code.
gcc 似乎在重载解析期间忽略了字段名称。这可以解释您所看到的奇怪行为,以及我们在删除构造函数时获得正确的诊断。
一段时间以来,人们已经能够在 GCC 中使用“指定初始化程序”:
struct CC{
double a_;
double b_;
};
CC cc{.a_ = 1., .b_ = 2.}; assert(cc.a_ == 1. and cc.b_ == 2.); // ok
CC cc{.bla = 0., .bli = 0.}; // compile error
但是,当我添加构造函数时,标签会被忽略。
struct CC{
double a_;
double b_;
CC(double a, double b) : a_{a}, b_{b}{}
};
CC cc{.a_ = 1., .b_ = 2.}; assert(cc.a_ == 1. and cc.b_ == 2.); // ok
CC cc{.b_ = 2., .a_ = 1.}; // compiles but labels don't matter only the order, confusing
CC cc{.bla = 2., .bli = 1.}; // compiles but labels don't matter, confusing
换句话说,带有构造函数的初始化语法使标签的行为就像注释一样!,这可能会让人非常困惑,但最重要的是,它很奇怪。
我无意中发现了这个,gcc 8.1 -std=c++2a
。
这是预期的行为吗?
参考:https://en.cppreference.com/w/cpp/language/aggregate_initialization
编辑 2022: 现在编译器支持 -std=c++20
行为是正确的。所有接受 -std=c++20
(10.1 及更高版本)的 GCC 版本也接受上述代码或在应该时给出错误。
编译器不应忽略指定初始值设定项的标签。所有这三个带有构造函数的示例都应该是错误格式的。
由于 C++20 指定初始化程序功能与 GCC 的 C 样式指定初始化程序之间存在冲突,您可能会出现此行为,因为 GCC 只是将它们提供给您,您正在隐式访问它们。如果 GCC 是正确的 C++20 兼容的,一旦你给类型一个构造函数,它就不再是一个聚合,因此指定的初始化器用法将是错误的。
基本上,这是一个由编译器默认为您提供的非 C++ 标准行为引起的驱动程序错误。如果您关闭此功能,很有可能会在这些情况下出现正确的编译器错误。
这是一个 gcc 错误,仍然可以构建 even with -pedantic in which we should receive warnings for any extensions
...to obtain all the diagnostics required by the standard, you should also specify -pedantic ...
和 gcc 声称支持 P0329R4: Designated initializers
对 C++2a
模式的提议 according to the C++ Standards Support in GCC page:
Language Feature | Proposal | Available in GCC?
...
Designated initializers | P0329R4 | 8
为了使用Designated initializers the type should be aggregate [dcl.init.list]p3.1:
If the braced-init-list contains a designated-initializer-list, T shall be an aggregate class. The ordered identifiers in the designators of the designated-initializer-list shall form a subsequence of the ordered identifiers in the direct non-static data members of T. Aggregate initialization is performed (11.6.1). [ Example:
struct A { int x; int y; int z; }; A a{.y = 2, .x = 1}; // error: designator order does not match declaration order A b{.x = 1, .z = 2}; // OK, b.y initialized to 0
—end example ]
CC
不是根据 [dcl.init.aggr] 的聚合:
An aggregate is an array or a class (Clause 12) with
- (1.1) — no user-provided, explicit, or inherited constructors (15.1),
....
gcc 错误报告
如果我们查看 gcc bug report: Incorrect overload resolution when using designated initializers,我们会在这个给定的示例中看到:
Another test case, reduced from Chromium 70.0.3538.9 and accepted by clang:
struct S { void *a; int b; }; void f(S); void g() { f({.b = 1}); }
This fails with
bug.cc: In function ‘void g()’: bug.cc:3:24: error: could not convert ‘{1}’ from ‘<brace-enclosed initializer list>’ to ‘S’ void g() { f({.b = 1}); } ^
The error suggests the field names are simply ignored entirely during overload resolution, which also explains the behaviour of the originally reported code.
gcc 似乎在重载解析期间忽略了字段名称。这可以解释您所看到的奇怪行为,以及我们在删除构造函数时获得正确的诊断。