Aggregate initialization of a union in C++ with `{}`
Asked Answered
M

1

6

In the following program the union U has two fields a and b, each with distinct default value. If one creates a variable of type U using aggregate initialization {} what are the value and the active member of the union?

#include <iostream>

struct A { int x = 1; };
struct B { int x = 0; };

union U {
    A a;
    B b;
};

int main() {
    U u{};
    std::cout << u.a.x;
}

Surprisingly the compilers diverge here: Clang prints 1 and GCC prints 0, demo: https://gcc.godbolt.org/z/8Tj4Y1Pv1

Is there a bug in one of the compilers or the behavior here is not defined by the standard?

Marlea answered 22/8, 2021 at 12:40 Comment(3)
When you initialize u you're doing value initialization. And since U is not a class type, it will lead to u being zero initialized.Desiree
Surprisingly the compilers diverge here: Clang prints 1 and GCC prints 0 - seems like defect, it should be 0Jarv
@Someprogrammerdude U is a class type, it is union aggregate class. Note the normative term "non-union aggregate", e.g. used in [dcl.init.aggr]/5. Thus, this is aggregate initialization and [dcl.init.aggr]/5 applies (/5.5 for U u{}; followed by /5.1 for the first data member a of the union).Ningpo
N
6

Clang is correct, GCC is wrong

As per [dcl.init.aggr]/1:

An aggregate is an array or a class ([class]) with

  • (1.1) no user-declared or inherited constructors ([class.ctor]),
  • (1.2) no private or protected direct non-static data members ([class.access]),
  • (1.3) no virtual functions ([class.virtual]), and
  • (1.4) no virtual, private, or protected base classes ([class.mi]).

A, B and U are all aggregate classes, although the prior to are non-union aggregate classes, which the former does not qualify as.

As per [dcl.init.aggr]/5 [emphasis mine]:

For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:

  • (5.1) If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
  • (5.2) Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
  • (5.3) Otherwise, the program is ill-formed.

If the aggregate is a union and the initializer list is empty, then

  • (5.4) if any variant member has a default member initializer, that member is initialized from its default member initializer;
  • (5.5) otherwise, the first member of the union (if any) is copy-initialized from an empty initializer list.

Thus

U u{};

is aggregate initialization, with the result that the first data member of the union class, namely the data member a of type A (which is a non-union aggregate class), is copy-initialized from an empty initializer list. As the single data member x of the type A has a default member initializer, then as per [dcl.init.aggr]/5.1 above, the data member x is initialized by its default member initializer.

Thus, Clang is correct, and GCC is wrong.


GCC bug report

Ningpo answered 22/8, 2021 at 13:32 Comment(3)
MSVC also prints 0Jarv
@MarcinPoloczek I generally give little value to MSVC's behaviour in the language's corner cases where GCC and Clang disagrees. In this particular case, afaict Clang is implementing the correct behavior as per the standard.Ningpo
Yes, your answer seems to be the correct one. I've missed the detail. I've removed my post.Jarv

© 2022 - 2024 — McMap. All rights reserved.