Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

POD structs containing constant member

With this code:

struct A
{
    int i;
    const int b;
};

// The union is to verify that A is a type that can be used in a union.
union U
{
    A a;
    int b;
};

int main()
{
    U a = {1, 1};
    U b = {2, 1};
}

g++ version 4.8.3 complains about an error:

a.cpp:9:4: error: member ‘A U::a’ with copy assignment operator not allowed in union
  A a;
    ^
a.cpp:9:4: note: unrestricted unions only available with -std=c++11 or -std=gnu++11

but clang 3.5.0 compiles this code without error. Which one is correct? Is this a compiler bug?

My attempt at solving this:

From the C++03 standard section 9.5 paragraph 1:

In a union, at most one of the data members can be active at any time, that is, the value of at most one of the data members can be stored in a union at any time. [Note: one special guarantee is made in order to simplify the use of unions: If a POD-union contains several POD-structs that share a common initial sequence (9.2), and if an object of this POD-union type contains one of the POD-structs, it is permitted to inspect the common initial sequence of any of POD-struct members; see 9.2. ] The size of a union is sufficient to contain the largest of its data members. Each data member is allocated as if it were the sole member of a struct. A union can have member functions (including constructors and destructors), but not virtual (10.3) functions. A union shall not have base classes. A union shall not be used as a base class. An object of a class with a non-trivial constructor (12.1), a non-trivial copy constructor (12.8), a non-trivial destructor (12.4), or a non-trivial copy assignment operator (13.5.3, 12.8) cannot be a member of a union, nor can an array of such objects. If a union contains a static data member, or a member of reference type, the program is ill-formed.

From the C++03 standard section 12.8 paragraphs 10 and 11:

If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. The implicitly-declared copy assignment operator for a class X will have the form X& X::operator=(const X&) if each direct base class B of X has a copy assignment operator whose parameter is of type const B&, const volatile B& or B, and for all the nonstatic data members of X that are of a class type M (or array thereof), each such class type has a copy assignment operator whose parameter is of type const M&, const volatile M& or M.

Otherwise, the implicitly declared copy assignment operator will have the form X& X::operator=(X&) ...

A copy assignment operator for class X is trivial if it is implicitly declared and if class X has no virtual functions (10.3) and no virtual base classes (10.1), and each direct base class of X has a trivial copy assignment operator, and for all the nonstatic data members of X that are of class type (or array thereof), each such class type has a trivial copy assignment operator; otherwise the copy assignment operator is non-trivial.

I'm not sure which compiler is correct because I don't know if a constant member has a trivial copy assignment operator.

Edit: The compilation commands are:

clang++ a.cpp -o a
g++ a.cpp -o a

Edit2: To show that g++ isn't complaining about A::b being const but A doesn't have a constructor, I also tried this program:

struct A
{
    int i;
    const int b;
};

int main()
{
    A a = {1, 1};
}

This compiled without errors on both g++ and clang++:

g++ b.cpp -o b
clang++ b.cpp -o b
like image 699
qbt937 Avatar asked Dec 27 '14 08:12

qbt937


1 Answers

As correctly noted by you the copy assignment operator is implicitly declared and trivial. Same goes for the default constructor, which is also trivial and implicitly declared.

Note though that both of these member functions aren't implicitly defined - that only happens when they are used, [class.ctor]/7:

An implicitly-declared default constructor for a class is implicitly defined when it is used to create an object of its class type (1.8).

.. which is clearly not the case here.
That's the key difference, and the reason that @dasblinkenlight's quote is irrelevant for this matter: The default constructor is never defined, thus the paragraph on missing mem-initializer-ids doesn't apply.

How are const members and the assignment operator connected then? Here:

An implicitly-declared copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type. A program is ill-formed if the class for which a copy assignment operator is implicitly defined has:

  • a nonstatic data member of const type, or [..]

Thus the program would be ill-formed if the copy assignment operator would be used. But it isn't. All the special member functions are solely declared, and any restrictions on the const-ness of non-static data members only apply on implicitly defined special member functions.

As an example, take

struct A
{
    int i;
    const int b;
};

int main()
{
    A a = {1, 1};
}

Which compiles fine under GCC. Your program should be well-formed, too, as all requirements on trivialty of special member functions of union members are met by A.

like image 101
Columbo Avatar answered Oct 26 '22 21:10

Columbo