【问题标题】:Constructor interferes with member variable designated initializer?构造函数干扰成员变量指定的初始化程序?
【发布时间】:2019-02-16 08:01:17
【问题描述】:

一段时间以来,人们已经能够在 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

【问题讨论】:

  • 即使使用-pedantic-errors -std=c++2a,我也可以在 GCC 8.1 上进行复制。呵呵。
  • 对我来说似乎是个错误。
  • 如果你在初始化器前加上= 有什么变化吗?
  • 我认为你只是在矩阵上撕开了一个洞。你是尼奥吗?
  • @Cale:在这种情况下,没有矩阵 ...它只是一个 GCC 扩展 ;)

标签: c++ g++ language-lawyer c++20 designated-initializer


【解决方案1】:

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

由于 C++20 指定初始值设定项功能和 GCC 的 C 样式指定初始值设定项之间的冲突,您很可能会遇到这种情况,由于 GCC 只是将它们提供给您,因此您正在隐式访问它们。如果 GCC 正确地兼容 C++20,一旦你给类型一个构造函数,它就不再是一个聚合,因此指定的初始化器用法将是错误的。

基本上,这是由编译器默认为您提供的非 C++ 标准行为引起的驱动程序错误。如果您关闭此功能,则很有可能会在这些情况下得到正确的编译器错误。

【讨论】:

  • “由于 C++20 指定初始值设定项功能和 GCC 的 C 样式指定初始值设定项之间的冲突,您可能会遇到这种情况”——不​​,GCC 只是从未实现对重载中的字段名称的支持分辨率,并且用户定义的构造函数的存在意味着重载分辨率开始发挥作用。最近我有一个不那么有趣的经历,弄清楚struct S { A a; B b; C c; }; / f({1, .c = 2}) 调用中出了什么问题,clang 会按照作者(不是我)的意图编译它,但 GCC 会尝试从2.
【解决方案2】:

这是一个 gcc 错误,它仍然构建 even with -pedantic 其中we should receive warnings for any extensions

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

gcc 声称支持P0329R4: Designated initializers 提案C++2a 模式according to the C++ Standards Support in GCC page

语言功能 |提案 |在 GCC 中可用吗?
...
指定初始化器 | P0329R4 | 8

为了使用Designated initializers,类型应该是聚合[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

——结束示例]

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

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

gcc 错误报告

如果我们查看gcc bug report: Incorrect overload resolution when using designated initializers,我们会在这个给定的示例中看到:

另一个测试用例,从 Chromium 70.0.3538.9 减少并被 铿锵声:

  struct S { void *a; int b; };
  void f(S);
  void g() { f({.b = 1}); }

这失败了

  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}); }
                        ^

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

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

【讨论】:

  • 我在现实中拥有的类本质上是聚合的,但我添加了一个构造函数来进行模板参数推导。我很高兴看到模板参数推导与指定的初始值设定项配合得非常好,直到我意识到它们(无论出于何种原因)只不过是本质上的 cmets。
  • @alfC 不要添加构造函数来启用CTAD,添加一个推导指南。也就是说,推导指南无论如何对指定的初始化器都没有帮助。
  • @Barry,我的印象是 CTAD 没有构造函数就无法工作。我会再试一次。
猜你喜欢
  • 1970-01-01
  • 2015-07-03
  • 2016-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多