Related: How to initialize a non-POD member in Union
The standard says
At most one non-static data member of a union may have a brace-or-equal-initializer.
But
struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union U {
int z;
double w;
Point p = Point(1,2);
};
#include <iostream>
int main () {
U u;
std::cout << u.p.x_ << ":" << u.p.y_ << std::endl;
}
prints 4196960:0
instead of the expected 1:2
.
I consider this a compiler bug. Is that so?
An initializer for a structure is a brace-enclosed comma-separated list of values, and for a union, a brace-enclosed single value. The initializer is preceded by an equal sign ( = ).
When initializing a union, the initializer list must have only one member, which initializes the first member of the union unless a designated initializer is used (since C99).
A union can be initialized on its declaration. Because only one member can be used at a time, only one can be initialized. To avoid confusion, only the first member of the union can be initialized.
In C99, you can use a designated union initializer: union { char birthday[9]; int age; float weight; } people = { . age = 14 }; In C++, unions can have constructors.
C++11 [class.ctor]/5 states:
A default constructor for a class
X
is a constructor of classX
that can be called without an argument. If there is no user-declared constructor for classX
, a constructor having no parameters is implicitly declared as defaulted (8.4). An implicitly-declared default constructor is aninline public
member of its class. A defaulted default constructor for classX
is defined as deleted if:
X
is a union-like class that has a variant member with a non-trivial default constructor,- any non-static data member with no brace-or-equal-initializer is of reference type,
- any non-variant non-static data member of const-qualified type (or array thereof) with no brace-or-equal-initializer does not have a user-provided default constructor,
X
is a union and all of its variant members are of const-qualified type (or array thereof),X
is a non-union class and all members of any anonymous union member are of const-qualified type (or array thereof),- any direct or virtual base class, or non-static data member with no brace-or-equal-initializer, has class type
M
(or array thereof) and eitherM
has no default constructor or overload resolution (13.3) as applied toM
’s default constructor results in an ambiguity or in a function that is deleted or inaccessible from the defaulted default constructor, or- any direct or virtual base class or non-static data member has a type with a destructor that is deleted or inaccessible from the defaulted default constructor.
A default constructor is trivial if it is not user-provided and if:
- its class has no virtual functions (10.3) and no virtual base classes (10.1), and
- no non-static data member of its class has a brace-or-equal-initializer, and
- all the direct base classes of its class have trivial default constructors, and
- for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.
Otherwise, the default constructor is non-trivial.
Since the struct Point
in the OP has a non-trivial default constructor,
Point() {}
a defaulted default constructor for a union containing a member of type Point
should be defined as deleted according to the first bullet:
X
is a union-like class that has a variant member with a non-trivial default constructor
resulting in the program presented in the OP being ill-formed.
However, the committee seems to consider this to be a defect in the case that a member of a union has a brace-or-equal-initializer, per core working group issue 1623:
According to 12.1 [class.ctor] paragraph 5,
A defaulted default constructor for class X is defined as deleted if:
X
is a union-like class that has a variant member with a non-trivial default constructor,...
X
is a union and all of its variant members are of const-qualified type (or array thereof),
X
is a non-union class and all members of any anonymous union member are of const-qualified type (or array thereof),...
Because the presence of a non-static data member initializer is the moral equivalent of a mem-initializer, these rules should probably be modified not to define the generated constructor as deleted when a union member has a non-static data member initializer. (Note the non-normative references in 9.5 [class.union] paragraphs 2-3 and 7.1.6.1 [dcl.type.cv] paragraph 2 that would also need to be updated if this restriction is changed.)
It would also be helpful to add a requirement to 9.5 [class.union] requiring either a non-static data member initializer or a user-provided constructor if all the members of the union have const-qualified types.
On a more general note, why is the default constructor defined as deleted just because a member has a non-trivial default constructor? The union itself doesn't know which member is the active one, and default construction won't initialize any members (assuming no brace-or-equal-initializer). It is up to the “owner” of the union to control the lifetime of the active member (if any), and requiring a user-provided constructor is forcing a design pattern that doesn't make sense. Along the same lines, why is the default destructor defined as deleted just because a member has a non-trivial destructor? I would agree with this restriction if it only applied when the union also has a user-provided constructor.
Issue 1623 has the status "drafting," indicating that the committee believes the issue is probably a defect - why else allow a brace-or-equal-initializer for a union member? - but hasn't yet devoted the time to determine the proper wording for a resolution. Indeed, the paragraph is largely the same in the current C++14 draft N3936 ([class.ctor]/4), except that the wording "any direct or virtual base class or non-static data member" is everywhere replaced by the simpler "any potentially constructed subobject."
Although the behavior of both compilers is not strictly conforming, I would consider Clang to be behaving in the spirit of the standard. It would appear that GCC becomes confused by the combination of deleted default constructor and brace-or-equal-initializer:
with the brace-or-equal-initializer present and maximum warnings GCC 4.8.2 performs no initialization of the union at all, and even warns that the members are used uninitialized:
main.cpp: In function 'int main()':
main.cpp:17:39: warning: 'u.U::p.Point::y_' is used uninitialized in this function [-Wuninitialized]
std::cout << u.p.x_ << ":" << u.p.y_ << std::endl;
^
main.cpp:17:22: warning: 'u.U::p.Point::x_' is used uninitialized in this function [-Wuninitialized]
std::cout << u.p.x_ << ":" << u.p.y_ << std::endl;
^
GCC should probably either conform to the standard and diagnose the program as ill-formed, or emulate clang's behavior and generate a proper constructor from the brace-or-equal-initializer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With