Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the defaulted default constructor deleted for a union or union-like class?

struct A{
    A(){}
};
union C{
   A a;
   int b = 0;
};
int main(){
    C c;
}

In the above code, GCC and Clang both complain that the default constructor for union C is defined as deleted.

However, the relevant rule says that:

A defaulted default constructor for class X is defined as deleted if:

  • X is a union that has a variant member with a non-trivial default constructor and no variant member of X has a default member initializer,
  • X is a non-union class that has a variant member M with a non-trivial default constructor and no variant member of the anonymous union containing M has a default member initializer,

Notice the emphasized wording. In the example, IIUC, since the variant member b has a default member initializer, the defaulted default constructor shouldn't be defined as deleted. Why do these compilers report this code as ill-formed?

If change the definition of C to

union C{
   A a{};
   int b;
};

Then all compilers can compile this code. The behavior hints that the rule actually means:

X is a union that has a variant member with a non-trivial default constructor and no default member initializer is supplied for the variant member

Is this a compiler bug or the vague wording of that rule?

like image 233
xmh0511 Avatar asked Dec 22 '20 06:12

xmh0511


People also ask

When should a default constructor be removed?

Deleting the default constructor of a class is a good idea when there are multiple choices for the default or uninitialised state. For example, suppose I have a class, template<typename F> class Polynomial; which represents a polynomial over a field, F .

What happens when default constructor is called?

In both Java and C#, a "default constructor" refers to a nullary constructor that is automatically generated by the compiler if no constructors have been defined for the class. The default constructor implicitly calls the superclass's nullary constructor, then executes an empty body.

What is a default constructor does every class have a default constructor?

A default constructor is a constructor that either has no parameters, or if it has parameters, all the parameters have default values. If no user-defined constructor exists for a class A and one is needed, the compiler implicitly declares a default parameterless constructor A::A() .

Can a default constructor be empty?

Providing an empty default constructor( private ) is necessary in those cases when you don't want an object of the class in the whole program. For e.g. the class given below will have a compiler generated default empty constructor as a public member . As a result, you can make an object of such a class.


Video Answer


1 Answers

This was changed between C++14 and C++17, via CWG 2084, which added the language allowing an NSDMI on (any) union member to restore the defaulted default constructor.

The example accompanying CWG 2084 though is subtly different to yours:

struct S {
  S();
};
union U {
  S s{};
} u;

Here the NSDMI is on the non-trivial member, whereas the wording adopted for C++17 allows an NSDMI on any member to restore the defaulted default constructor. This is because, as written in that DR,

An NSDMI is basically syntactic sugar for a mem-initializer

That is, the NSDMI on int b = 0; is basically equivalent to writing a constructor with mem-initializer and empty body:

C() : b{/*but use copy-initialization*/ 0} {}

As an aside, the rule ensuring that at most one variant member of the union has an NSDMI is somewhat hidden in a subclause of class.union.anon:

4 - [...] At most one variant member of a union may have a default member initializer.

My supposition would be that since gcc and Clang already allow the above (the NSDMI on the non-trivial union member) they didn't realize that they need to change their implementation for full C++17 support.

This was discussed on the list std-discussion in 2016, with an example very similar to yours:

struct S {
    S();
};
union U {
    S s;
    int i = 1;
} u;

The conclusion was that clang and gcc are defective in rejecting, although there was at the time a misleading note, amended as a result.

For Clang, the bug is https://bugs.llvm.org/show_bug.cgi?id=39686 which loops us back to SO at Implicitly defined constructor deleted due to variant member, N3690/N4140 vs N4659/N4727. I can't find a corresponding bug for gcc.

Note that MSVC correctly accepts, and initializes c to .b = 0, which is correct per dcl.init.aggr:

5 - [...] 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; [...]
like image 135
ecatmur Avatar answered Sep 20 '22 01:09

ecatmur