3
votes

Having this structure:

struct A {
    struct B {
        int a = 21;
        int b;
        int c = 22;
        int d;
        int e = 23;
    };
    B b1  = { 11, 12 };
    B b2  = { 11, 12, 13 };
    int x;
};

And declaring:

A a = { { 1, 2, 3, 4 }, { 1 }, 5 };

According to both Clang (3.8.0) and GCC (5.4.0), these are the values of the 8 possible combinations (a.b1.e and a.b2.a are repeated cases), regarding where the initial value is taken from (or not), :

a.b1.a = 1   // 111
a.b1.b = 2   // 110
a.b1.c = 3   // 101
a.b1.d = 4   // 100
a.b2.b = 0   // 010    // Why not 12 instead of  0? -> Explained in N3605
a.b2.c = 22  // 011    // Why not  0 instead of 22 ?  Why not 13 ?
a.b2.d = 0   // 000
a.b2.e = 23  // 001    // Why not  0 instead of 23 ?

Taking into account the example in N3605 and the C++14 Standard (ISO/IEC 14882:2014), Section 8.5.1, Paragraph 7:

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).

I assume case 010 is correct. Then, why cases 011 (a.b2.c) and 001 (a.b2.e) are not also equal to zero? Case 010 is zero because a.b2 "does have an initializer", therefore "the non-static data member initializer is ignored" (N3605 again). Why the default member initializers are not ignored as well?

In fact, reading the C++14 Standard quote it would make more sense to me that case 010 would be 12 (it is zero) and cases 011 and 001 would be zero (as they actually are). So what I don't understand is why a.b2 is sometimes considered to "have an initializer" and other times it is not.

2
The { 1 } "replaces" the { 11, 12, 13 }. The inner default initializers still apply unless the init-list overrides them (which happens to b2.a, but not b2.c and b2.e). - Baum mit Augen♦

2 Answers

6
votes

You declare a with initializers for all its members: b1, b2 and x. That means we construct as if

a.b1 = B{ 1, 2, 3, 4 };
a.b2 = B{ 1 };
a.x = 5;

B's definition says that B{ 1, 2, 3, 4 } means B{ 1, 2, 3, 4, 23 } and that B{ 1 } means B{ 1, 0, 22, 0, 23 }. And that's exactly the result you get.


If you had written

A a = { { 1, 2, 3, 4 }, };

then a.b2 would have been initialized using its default of { 11, 12 }:

a.b1 = B{ 1, 2, 3, 4 };
a.b2 = B{ 11, 12 };
a.x = {};

It may help to think of those brace-expressions such as { 1 } and { 11, 12 } in your example as being fully-constructed B objects, long before A's constructor even sees them.

2
votes

In this example {1, 2, 3, 4} is an initializer for B b1. The compiler already has an initializer, so now it won't look at { 11, 12 } anymore.