从初始值设定项列表转换为“A”将使用显式构造函数“A::A(int)”

问题描述 投票:0回答:1

我正在尝试将旧的 C++03 代码库迁移到 C++11。但我无法理解 gcc 在以下情况下警告我的内容:

% g++ -std=c++03 t.cxx
% g++ -std=c++11 t.cxx
t.cxx: In function ‘int main()’:
t.cxx:8:21: warning: converting to ‘A’ from initializer list would use explicit constructor ‘A::A(int)’
    8 | int main() { B b = {}; }
      |                     ^
t.cxx:8:21: note: in C++11 and above a default constructor can be explicit
struct A {
  explicit A(int i = 42) {}
};
struct B {
  A a;
};
    
int main() {
  B b = {};
  return 0;
}

我在这里尝试做的是基本的零初始化。这对于 C++03 似乎是合法的,但我无法理解如何在 C++11 中表达等价物。


仅供参考,我正在使用:

% g++ --version
g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
c++ c++11 gcc-warning
1个回答
8
投票

给定的程序是格式错误,原因如下所述。

C++20

B
是一个聚合。由于您没有显式初始化
a
,因此dcl.init.aggr#5适用:

  1. 对于非联合聚合,每个不是显式初始化元素的元素都按如下方式初始化:

5.2 否则,如果该元素不是引用,则该元素会从空初始化器列表复制初始化([dcl.init.list])。

这意味着

a
是从空初始化器列表中复制初始化的。换句话说,就好像我们在写:

A a = {}; // not valid see reason below

现在我们进入dcl.init.list#3.5:

否则,如果初始值设定项列表没有元素并且 T 是具有默认构造函数的类类型,则该对象是 值初始化

这意味着该对象将被初始化。

现在进行值初始化

对 T 类型的对象进行值初始化意味着:

  • 如果 T 是一个(可能是 cv 限定的)类类型 ([class]),那么
    • 如果 T 没有默认构造函数 ([class.default.ctor]) 或用户提供或删除的默认构造函数,则该对象是 default-initialized;

所以我们进入默认初始化

如果 T 是一个(可能是 cv 限定的)类类型 ([class]),则考虑构造函数。枚举适用的构造函数 ([over.match.ctor]),初始化器 () 的最佳构造函数是通过重载决策([over.match])选择。 使用空参数列表调用如此选择的构造函数来初始化对象。

最后来自over.match.ctor

当类类型的对象被直接初始化、从相同或派生类类型 ([dcl.init]) 的表达式复制初始化或默认初始化时,重载决策会选择构造函数。 对于不在复制初始化上下文中的直接初始化或默认初始化,候选函数是正在初始化的对象的类的所有构造函数。 对于复制初始化(包括复制初始化上下文中的默认初始化),候选函数是该类的所有转换构造函数([class.conv.ctor])。 参数列表是初始化器的表达式列表或赋值表达式。

这意味着只有转换因子才是候选者。由于

A::A(int)
是显式的,它不是一个转换向量,因此候选集是空的,并且程序(
A a ={};
)格式不正确。


本质上,失败的原因是

A a = {};
格式不正确。


解决方案

为了解决这个问题,我们可以在列表中传递

A{}
A{0}
作为初始化器,如下所示:

B b = { A{} };  //ok now 
B c = { A{0} }; //also ok

工作演示


注意

请注意,另一方面,编写

A a{};
是格式良好的,因为这是一个直接初始化上下文,因此它是 direct-list-initialization

© www.soinside.com 2019 - 2024. All rights reserved.