10

Consider the following snippet:

#include <iostream>
union U{
    U(): i(1) {}
    int i;
    int j = 2;    // this default member initializer is ignored by the compiler
};

U u;

int main(){
    std::cout << u.i << '\n';
    std::cout << u.j << '\n';
}

The code prints (see live example):

1
1

Where in the Standard does it say that the default member initializer for the member U::j is ignored by the compiler?

Note that the union below doesn't compile and this is OK according to [class.union.anon]/4. I was thus expecting the snippet above also not to compile.

See live example:

union U{
    int i = 1;
    int j = 2;
};
Ayrosa
  • 3,385
  • 1
  • 18
  • 29

2 Answers2

7

Where in the Standard does it say that the default member initializer for the member U::j is ignored by the compiler?

See [class.base.init] paragraph 9 bullet 9.1 in the C++17 CD.

N.B. your demo has undefined behaviour, because the active member of the union is i but you read from j. This works with some compilers as a non-standard extension, but is not allowed in ISO C++.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • I believe you're referring to [\[class.base.init\]/9](http://eel.is/c++draft/class.base.init#9), but I don't think it applies to the snippet above according to this sentence: `... if a given potentially constructed subobject is not designated by a mem-initializer-id ...`. I also would say that in this particular case the code is probably not UB, as `i` and `j`, have the same address in memory and both have the type `int`. – Ayrosa Nov 16 '16 at 13:42
  • Why the example in [class.mem]/23 is OK but that above demo is not? – Danh Nov 16 '16 at 13:52
  • The example in [class.mem]/23 is OK, as the member `t1` is the active member of the union. Note that member `t2` is not initialized in the example. – Ayrosa Nov 16 '16 at 13:56
  • 1
    @Ayrosa For `j`, the first bullet in [class.base.init]/9 doesn't apply, because the condition that "no other variant member of that union is designated by a mem-initializer-id" isn't met, so you fall through to the second bullet, which says no initialization is performed. – T.C. Nov 16 '16 at 18:39
  • @T.C. I see what you mean. But even so, the second bullet says "no initialization is performed", and as far as I can understand, this includes the member `i`, which was initialized as the union active member, as the code shows. How do you explain this contradiction? – Ayrosa Nov 16 '16 at 19:28
  • 1
    @Ayrosa "No initialization is performed" for that potentially constructed subobject, i.e., `j`. That says nothing about `i`. – T.C. Nov 16 '16 at 19:29
  • @T.C. [class.base.init]/9 `In a non-delegating constructor, if a given potentially constructed subobject is not designated by a mem-initializer- id ...` I don't think the type `int` satisfies the condition of being a potentially constructed subobject. – Ayrosa Nov 16 '16 at 19:37
  • @T.C. I have just verified that a member of type int satisfies the condition above according to [special]/5. You deserve the credit for the answer. Thanks. – Ayrosa Nov 16 '16 at 19:40
  • You asked where in the standard, so that's what I answered. It's not my fault if you don't interpret the wording correctly :-) – Jonathan Wakely Nov 17 '16 at 18:16
  • @JonathanWakely deleted – Tomilov Anatoliy Nov 18 '16 at 01:48
  • @JonathanWakely Do you mean it worth discussion? – Tomilov Anatoliy Nov 18 '16 at 04:29
  • @Orient, that's up to you, but I don't want to discuss it, and it doesn't belong in these comments. – Jonathan Wakely Nov 18 '16 at 12:20
3

Note that you are declaring a union object, where all members share the same memory area - member variables turn into different "typed views" of the same data.

Thus, as members i and j are effectively stored in the same memory location, any initialization you perform on j (with the initializer) will be overwritten by your constructor setting i.

Just for test, remove the initialization of i from the constructor:

#include <iostream>
union U{
    U() {}
    int i;
    int j = 2;    // this initializes both i & j
};

U u;

int main(){
    std::cout << u.i << '\n';
    std::cout << u.j << '\n';   
}

The output would be

2
2

Update: As per @Ayrosa comments and being just intrigued, I modified the original snippet to perform some initialization with a function (instead of a constant) to induce side effects.

#include <iostream>

int static someStatic()
{
    std::cout << "Initializer was not ignored\n";
    return(2);
}

union U{
    U(): i(1) {}
    int i;
    int j = someStatic();    // this default member initializer is ignored by the compiler
};

U u;

int main(){
    std::cout << u.i << '\n';
    std::cout << u.j << '\n';
}

Result was:

1
1

Meaning that the call to someStatic() was, in fact, ignored by the compiler.

  • The difference between my example and yours is that in my case, I'm trying to "activate" two members of the union which is prohibited by [\[class.union.anon\]/4](http://eel.is/c++draft/class.union.anon#4), while in yours, you're "activating" just one member `j` which is legal. But contrary to my expectation, the compiler ignores the initialization with the default member initializer, similar to what happens with a non-union class. In this last case, [\[class.base.init\]/10](http://eel.is/c++draft/class.base.init#10) (to be continued) . . . – Ayrosa Nov 16 '16 at 16:53
  • specifies that the default member initializer should be ignored. I was expecting to find something similar for a union. – Ayrosa Nov 16 '16 at 16:53
  • I guess the way of checking it is by calling some function elsewhere that sets a flag and returns the intended initialization value for j (`int j=somestatic();`). As the constructor will efectively change both members if the union (and destroy j's initialized value) is the only way to verify if the compiler is, in fact, ignoring the initializer – Fernando Marcos Nov 16 '16 at 18:09