显式默认构造函数

22

这段代码可以在GCC 5.X和MSVC中编译通过,但是在GCC 6.X中会出现错误:

"converting to 'a' from initializer list would use explicit constructor 'a::a()'",Clang则提示"chosen constructor is explicit in copy-initialization".

移除explicit或者将其修改为a c{}可以解决问题,但我很好奇它为什么会这样工作。

class a
{
public:
    explicit a () {}
};
struct b
{
    a c;
};

int main() {
    b d{};
}

使用 b d; 或者 b d{a{}} 同样可以工作。 - Yi Tang
1个回答

25

b是一个聚合体。当你使用初始化列表对其进行初始化时,列表中的元素将初始化聚合体的前n个成员,其中n是列表中元素的数量。聚合体的剩余元素将进行复制列表初始化

因此,在您的示例中,c将进行复制列表初始化,但如果选择的构造函数是explicit,则会出现错误。

相关的标准引用如下:

[dcl.init.aggr]/3

当使用[dcl.init.list]中指定的初始化器列表初始化聚合体时,初始化器列表的元素将作为聚合体元素的初始化器。聚合体的显式初始化元素如下确定:
...
- 如果初始化器列表是一个初始化器列表,则聚合体的显式初始化元素是聚合体的前n个元素,其中n是初始化器列表中元素的数量。
- 否则,初始化器列表必须为{},并且没有显式初始化元素。

[dcl.init.aggr]/5

对于非联合聚合体,每个未显式初始化的元素的初始化如下: ... - 否则,如果该元素不是引用,则从空初始化列表[dcl.init.list]复制初始化该元素。
从空初始化列表[dcl.init.list]中复制初始化的效果如下:

[dcl.init.list]/3

对于类型为T的对象或引用的列表初始化定义如下:
...
- 否则,如果初始化程序列表没有元素且T是具有默认构造函数的类类型,则该对象将进行值初始化。

[dcl.init]/8

对于类型为T的对象进行值初始化的意思是:
...
- 如果T是一个(可能带有cv限定符的)类类型,它没有默认构造函数([class.ctor])或者该默认构造函数是用户提供的或已删除,则该对象将进行默认初始化;

[dcl.init]/7

对于类型T进行默认初始化意味着: - 如果T是一个(可能带有cv限定符的)类类型,则会考虑构造函数。适用的构造函数将被枚举([over.match.ctor]),并通过重载分辨选择最佳的初始化器()。因此,所选的构造函数将被调用,使用空参数列表来初始化对象。

[over.match.ctor]

对于复制初始化,候选函数是该类的所有转换构造函数。

[class.conv.ctor]/1

一个没有使用function-specifier explicit声明的构造函数,指定了从其参数类型(如果有)到其类类型的转换。这样的构造函数被称为转换构造函数
在上面的示例中,a没有转换构造函数,因此重载解析失败。在[class.conv.ctor]/2中的(非规范性的)示例甚至包含了一个非常相似的情况。
  struct Z {
    explicit Z();
    explicit Z(int);
    explicit Z(int, int);
  };

  Z c = {};                       // error: copy-list-initialization

您可以通过为c提供默认成员初始化器来避免错误。

struct b
{
    a c{};  // direct-list-initialization, explicit ctor is OK
};

1
@DanielLangr 目前提到复制初始化的措辞是作为指定初始化器更改的一部分引入的。如果我将其与 N4140(C++14 草案)进行比较,先前的措辞是“如果没有花括号或等号初始化程序,从空初始化程序列表中”,我认为这可能被解释为直接列表初始化。这可能是旧编译器接受它的原因。我找不到“复制初始化”被引入的具体原因。 - Praetorian
你从 [over.match.list] 中“随机”选择了一条规则,但没有说明为什么这条规则适用。你也没有展示出 [dcl.init.list] 中相关的措辞链。因此,这个引用基本上是无用的。我不明白:为什么这个规则会生效? - Johannes Schaub - litb
@JohannesSchaub-litb 更好了吗? - Praetorian
2
over.match.ctor 表示:“对于直接初始化或非复制初始化的默认初始化,候选函数是正在初始化的对象类的所有构造函数。对于复制初始化,候选函数是该类的所有转换构造函数。参数列表是初始化程序的表达式列表或赋值表达式。” 这意味着我们只考虑转换构造函数。 - Johannes Schaub - litb
@JohannesSchaub-litb 谢谢,我想我明白你指向我的方向了。我一直试图访问 over.match.list,主要是因为它符合错误消息,但似乎 不适用。class.conv.ctor 本身表示在这种情况下 a 没有可行的构造函数。 - Praetorian
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接