Question

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?

Was it helpful?

Solution

C++11 [class.ctor]/5 states:

A default constructor for a class X is a constructor of class X that can be called without an argument. If there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared as defaulted (8.4). An implicitly-declared default constructor is an inline public member of its class. 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,
  • 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 either M has no default constructor or overload resolution (13.3) as applied to M’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:

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top