Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does the C++ standard allow zero-initialization of a POD object with const members?

I've defined a POD that I'm planning on using as an immutable data store. To accomplish this, I've qualified its members as const, and am expecting to value-initialize instances (and zero-initialize in some cases). Consider the following code:

struct Foo
{
    const int value;
};

int main()
{
    Foo foo{ };

    return 0;
}

When I try to zero-initialize this POD, I get a compiler error in Visual Studio (C3852) because of the const qualifier on Foo::value. If I remove the qualifier, the code compiles fine.

The exact error message is:

error C3852: 'Foo::value' having type 'const int': aggregate initialization could not initialize this member const members cannot be default initialized unless their type has a user defined default constructor

According to the standard (draft n3337), §8.5/5 (zero-initialization):

To zero-initialize an object or reference of type T means:

— if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;

— if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;

— if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zeroinitialized and padding is initialized to zero bits;

— if T is an array type, each element is zero-initialized;

— if T is a reference type, no initialization is performed.

and §8.5/6 (default-initialization):

To default-initialize an object of type T means:

— if T is a (possibly cv-qualified) class type (Clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);

— if T is an array type, each element is default-initialized;

— otherwise, no initialization is performed. If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.

and §8.5/7 (value-initialization):

To value-initialize an object of type T means:

— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);

— if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.

— if T is an array type, then each element is value-initialized;

— otherwise, the object is zero-initialized.

My reading of the standard leads me to believe that my POD should be zero-initialized; not default-initialized. Am I misunderstanding the initialization process described in the standard?

EDIT: Considering the details provided in the accepted answer and related comments, this looks like a potential bug in the VS implementation (i.e., the implementation may be based on an outdated version of the standard). I've created a Microsoft Connect ticket to track this, which can be found here:

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

like image 547
Lilshieste Avatar asked Apr 03 '14 21:04

Lilshieste


People also ask

Are class members initialized to zero?

If T is scalar (arithmetic, pointer, enum), it is initialized from 0 ; if it's a class type, all base classes and data members are zero-initialized; if it's an array, each element is zero-initialized.

Does default constructor zero initialize?

For built-in types it results in zero-initialization.

What is zero initialization?

Setting the initial value of an object to zero is called the zero initialization.

Does unordered map initialize 0?

According to cppreference.com, std::map::operator[] for non-existing value does zero-initialization.


1 Answers

@dyp's comment that aggregate initialization occurs is correct, but leads to zero-initialization of the member.

Foo foo{ };

is list-initialization, so we start at 8.5.4. 8.5.4p3 says that (ordering from draft n3690, which matches C++11, and still in n3797)

List-initialization of an object or reference of type T is defined as follows:

  • If T is an aggregate, aggregate initialization is performed (8.5.1).
  • Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
  • Otherwise, if T is a specialization of std::initializer_list<E>, a prvalue initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5).
  • Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
  • Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.
  • Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized or direct-list-initialized, depending on the kind of initialization for the reference, and the reference is bound to that temporary.
  • Otherwise, if the initializer list has no elements, the object is value-initialized.
  • Otherwise, the program is ill-formed.

In the first case, we must visit 8.5.1 for aggregate initialization of the class. p7:

If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from its brace-or-equal-initializer or, if there is no brace-or-equal-initializer, from an empty initializer list (8.5.4).

So the value member gets list-initialized from an empty initializer list, which recurses into the above rule, reaching the penultimate case, that of value initialization. And of course from the rules in the question, that is zero-initialization.


dyp mentions that prior to n3485, which implemented CWG 1301, the rule for default construction took precedence, and would try and fail to access the deleted default constructor.

like image 131
Ben Voigt Avatar answered Sep 24 '22 02:09

Ben Voigt