Structures and Member Initializers in C++

Since C++11, one can put the initialization of data members directly into structure or class definition. However, this change has consequences with regards to the construction of such structures. Indeed, by adding member initializers into a structure may render your previously working code non-compilable. In the present post, I describe this change, the related issues, and explain how to get around them.

Constructing C-Like Structures

Given a C-like structure:

struct A {
    int i;
    double j;
};

we can either construct it without any initializer:

A a; // OK, but a.i and a.j have indeterminate values (assuming local scope)

or we can specify an initializer in curly brackets:

A b = {};       // OK, b.i is 0 and b.j is 0.0 (even in local scope)
A c = {1};      // OK, c.i is 1 and c.j is 0.0 (even in local scope)
A d = {1, 2.0}; // OK, d.i is 1 and d.j is 2.0

This code works even in ANSI C (C89). Indeed, this type of structure initialization comes from C. We only have to add struct before A because in C, structure names are placed in a separate namespace:

struct A b = {};       // C89; same as C++ above
struct A c = {1};      // C89; same as C++ above
struct A d = {1, 2.0}; // C89; same as C++ above

Adding Member Initializers

Since C++11, we can put the initialization of data members directly into the definition of the structure or class (so-called member initializers):

struct A {
    int i = 0;
    double j = 0.0;
};

Then, when we do not specify any initializer, the above default values are automatically used:

A a; // OK (C++11), a.i is 0 and a.j is 0.0

However, surprisingly, the following code now fails to compile:

A c = {1};      // Fails to compile (C++11)!
A d = {1, 2.0}; // Fails to compile (C++11)!

For example, GCC 5 emits the following error messages:

error: could not convert ‘{1}’ from ‘<brace-enclosed initializer list>’ to ‘A’
error: could not convert ‘{1, 2.0e+0}’ from ‘<brace-enclosed initializer list>’ to ‘A’

What is even more surprising is that under C++14, the code works as before:

A c = {1};      // OK (C++14), c.i is 1 and c.j is 0.0
A d = {1, 2.0}; // OK (C++14), d.i is 1 and d.j is 2.0

Why Does It Not Compile Under C++11?

To be able to use the C structure initialization via curly braces, the structure needs to be an aggregate. In terms of structures, this means that the structure cannot have any private/protected members, user-provided constructors, base classes, or virtual member functions (see this great post for an explanation). However, C++11 added a new requirement: for a structure to be an aggregate, there cannot be any initializers for non-static members [ISO C++11, 8.5.1, §1].

Since we have added member initializers, our structure is no longer an aggregate. This means that when we write

A c = {1};

it is no longer aggregate initialization. Instead, the compiler is forced to use so-called brace-init-list initialization, which essentially gives the code the following meaning:

A c = A(1);

The rewritten code makes the issue obvious. We are trying to construct A through a single-int-parameter constructor. However, A has only the default constructor (taking no parameters), created automatically by the compiler. Thus, there is no matching constructor. Indeed, if we try to compile the code through Clang, it emits the following error:

error: no matching constructor for initialization of 'A'

Why Does It Compile Under C++14?

C++14 relaxed this restriction in N3653 by allowing aggregates to have member initializers. The original proposal is N3605. Based on the original proposal, we can argue that the motivation behind this restriction was that member initializers are essentially kind of a user-defined constructor. However, as it often confused people by breaking previously working code, the restriction was removed in C++14.

Note: If you try to compile the code with GCC 4.9, it will not compile, even with -std=c++14. The reason is that this C++14 change is supported only by GCC 5 and higher.

How To Get Around This Restriction In C++11 or GCC 4.9?

If you have to use C++11 or are stuck with GCC 4.9, you would like to know whether there are other solutions than upgrading to C++14. The answer is yes. Essentially, you have two options:

  • Provide a constructor. Simply provide a constructor:
    struct A {
        A(int i = 0, double j = 0.0): i(i), j(j) {}
    
        int i;
        double j;
    };
    
    A a;            // OK (C++11/14), a.i is 0 and a.j is 0.0
    A b = {};       // OK (C++11/14), b.i is 0 and b.j is 0.0
    A c = {1};      // OK (C++11/14), c.i is 1 and c.j is 0.0
    A d = {1, 2.0}; // OK (C++11/14), d.i is 1 and d.j is 2.0
    

    A downside is that because of the user-provided constructor, the structure is now no longer an aggregate. However, this matters only when you really need it to be an aggregate (reasons).

  • Stop using member initializers. Remove member initializers from the structure and always explicitly initialize its instances during creation. An upside is that this solution keeps the structure an aggregate. A major downside is that it is easy to forget to initialize the structure, thus allowing programming errors to appear.

Source Code

The complete source code for the present post is available on GitHub.

Leave a Comment.