C++标准是否允许使用const成员对POD对象进行零初始化?

Lil*_*ste 13 c++ language-lawyer c++11

我已经定义了一个POD,我计划将其用作不可变数据存储.为了实现这一点,我已经对其成员进行了限定const,并期望对实例进行值初始化(在某些情况下为零初始化).请考虑以下代码:

struct Foo
{
    const int value;
};

int main()
{
    Foo foo{ };

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当我尝试对此POD进行零初始化时,由于符合条件,我在Visual Studio(C3852)中遇到编译器错误.如果我删除限定符,代码编译正常.constFoo::value

确切的错误消息是:

错误C3852:'Foo :: value'类型为'const int':聚合初始化无法初始化此成员const成员不能默认初始化,除非它们的类型具有用户定义的默认构造函数

根据标准(草案n3337),§8.5/ 5(零初始化):

零初始化T类型的对象或引用意味着:

- 如果T是标量类型(3.9),则将对象设置为值0(零),作为整数常量表达式,转换为T;

- 如果T是(可能是cv限定的)非联合类类型,则每个非静态数据成员和每个基类子对象都是零初始化的,并且填充初始化为零位;

- 如果T是(可能是cv限定的)联合类型,则对象的第一个非静态命名数据成员为零初始化,并且填充初始化为零位;

- 如果T是数组类型,则每个元素都是零初始化的;

- 如果T是引用类型,则不执行初始化.

和§8.5/ 6(默认初始化):

默认初始化T类型的对象意味着:

- 如果T是一个(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的);

- 如果T是数组类型,则每个元素都是默认初始化的;

- 否则,不执行初始化.如果程序要求对const限定类型T的对象进行默认初始化,则T应为具有用户提供的默认构造函数的类类型.

和§8.5/ 7(值初始化):

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

- 如果T是具有用户提供的构造函数(12.1)的(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的);

- 如果T是一个(可能是cv限定的)非联合类类型而没有用户提供的构造函数,那么该对象是零初始化的,如果T的隐式声明的默认构造函数是非平凡的,则调用该构造函数.

- 如果T是数组类型,则每个元素都是值初始化的;

- 否则,对象被零初始化.

我对标准的阅读使我相信我的POD应该是零初始化的; 没有默认初始化.我是否误解了标准中描述的初始化过程?

编辑:考虑到接受的答案和相关评论中提供的详细信息,这看起来像VS实现中的潜在错误(即,实现可能基于标准的过时版本).我已经创建了一个Microsoft Connect票据来跟踪它,可以在这里找到:

https://connect.microsoft.com/VisualStudio/feedback/details/846222/c-compiler-uses-incorrect-initialization-scheme-for-certain-objects

Ben*_*igt 11

@dyp的注释表明聚合初始化发生是正确的,但会导致成员的零初始化.

Foo foo{ };
Run Code Online (Sandbox Code Playgroud)

是列表初始化,所以我们从8.5.4开始.8.5.4p3说(从草案n3690订购,与C++ 11匹配,仍然在n3797)

列表初始化对象或类型T的引用定义如下:

  • 如果T是聚合,则执行聚合初始化(8.5.1).
  • 否则,如果初始化列表没有元素并且T是具有默认构造函数的类类型,则对象将进行值初始化.
  • 否则,如果T是特化std::initializer_list<E>,initializer_list则如下所述构造prvalue 对象,并用于根据相同类型的类初始化对象的规则初始化对象(8.5).
  • 否则,如果T是类类型,则考虑构造函数.枚举适用的构造函数,并通过重载决策(13.3,13.3.1.7)选择最佳构造函数.如果转换任何参数需要缩小转换(见下文),则程序格式错误.
  • 否则,如果初始化列表具有单个元素类型E且且T不是引用类型或其引用类型与引用相关E,则从该元素初始化对象或引用; 如果将元素转换为需要缩小转换(见下文)T,则程序格式不正确.
  • 否则,如果T是引用类型,则引用类型的prvalue临时值T是copy-list-initialized或direct-list-initialized,具体取决于引用的初始化类型,并且引用绑定到该临时值.
  • 否则,如果初始化列表没有元素,则对象进行值初始化.
  • 否则,该计划是不正确的.

在第一种情况下,我们必须访问8.5.1以进行类的聚合初始化.P7:

如果列表中的initializer-clause少于聚合中的成员,那么未明确初始化的每个成员都应从其brace-or-equal-initializer初始化,或者如果没有bra-or-equal-initializer,从空的初始化列表(8.5.4).

因此,value成员从空的初始化列表中获取列表初始化,该列表将递归到上述规则,达到倒数第二种情况,即值初始化.当然,从问题中的规则来看,这是零初始化.


dyp提到在实现CWG 1301的 n3485之前,默认构造的规则优先,并且会尝试并且无法访问已删除的默认构造函数.