构造函数干扰成员变量指定的初始化程序?

alf*_*lfC 11 c++ g++ designated-initializer language-lawyer c++20

有一段时间了,人们已经能够在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
Run Code Online (Sandbox Code Playgroud)

但是,当我添加构造函数时,标签将被忽略.

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
Run Code Online (Sandbox Code Playgroud)

换句话说,带有构造函数的初始化器语法使得标签的行为就像注释一样!这可能非常令人困惑,但最重要的是,这很奇怪.

我偶然发现了这个gcc 8.1 -std=c++2a.

这是预期的行为吗?

参考:https://en.cppreference.com/w/cpp/language/aggregate_initialization

Nic*_*las 7

编译器不应忽略指定初始值设定项的标签.带构造函数的所有这三个示例都应该是格式错误的.

您可能会因为C++ 20指定的初始化程序功能与GCC的C样式指定初始化程序之间的冲突而导致此行为,由于GCC只是将它们提供给您,因此您可以隐式访问它们.如果GCC是正确的C++ 20-compiliant,一旦你给了类型一个构造函数,它将不再是一个聚合,因此指定的初始化程序使用将是不正确的.

基本上,这是由非C++引起的驱动程序错误 - 默认情况下编译器为您提供的标准行为.如果你关闭这个功能,你可能会遇到适当的编译错误.

  • "由于C++ 20指定的初始化程序功能与GCC的C风格指定初始化程序之间存在冲突,您可能会遇到这种情况" - 不,GCC从未在重载解析中实现对字段名称的支持,并且存在用户 - 定义的构造函数意味着重载分辨率发挥作用.我最近找到了一个结构S {A a; B b; C c; };`/`f({1,.c = 2})`call,其中clang将编译为作者(不是我)的意图,但GCC会尝试从`2`初始化`b`. (2认同)

Sha*_*our 5

这是一个gcc错误,即使使用-pedantic仍然可以构建,我们应该收到任何扩展的警告

...要获得标准所需的所有诊断,您还应指定-pedantic ...

并且gcc声称根据GCC页面中的C++标准支持支持模式P0329R4: Designated initializers提议:C++2a

语言特写| 提案| GCC有哪些?
...
指定的初始化器| P0329R4 | 8

为了使用指定的初始值设定项,类型应该是aggregate [dcl.init.list] p3.1:

如果braced-init-list包含指定的初始化列表,则T应为聚合类.指定初始化列表的指示符中的有序标识符应形成T的直接非静态数据成员中的有序标识符的子序列.执行聚合初始化(11.6.1).[例如:

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
Run Code Online (Sandbox Code Playgroud)

- 末端的例子]

CC根据[dcl.init.aggr]不是聚合:

聚合是一个数组或类(第12条),
- (1.1) - 没有用户提供的,显式的或继承的构造函数(15.1),
....

gcc bug报告

如果我们看一下gcc bug报告:在使用指定的初始化器时,我们在这个给定的例子中看到了不正确的重载决策:

另一个测试案例,从Chromium 70.0.3538.9减少并被clang接受:

  struct S { void *a; int b; };
  void f(S);
  void g() { f({.b = 1}); }
Run Code Online (Sandbox Code Playgroud)

这失败了

  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}); }
                        ^
Run Code Online (Sandbox Code Playgroud)

该错误表明在重载解析期间完全忽略了字段名称,这也解释了最初报告的代码的行为.

似乎gcc在重载解析期间忽略字段名称.这将解释您所看到的奇怪行为,并在我们删除构造函数时获得正确的诊断.