Question

Looking at the definition of trivial default constructor in the standards:

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.

It seems that the definition of a default constructor's triviality doesn't rule out the possibility of a deleted default constructor:

struct A {
    int& a;  // the implicitly defaulted default constructor will be defined as deleted
};

struct B {
    B()=delete;  // explicitly deleted
};

int main() {
    static_assert(is_trivial<A>::value, "");
    static_assert(is_trivial<B>::value, "");
}

The above code runs without any assertion failure. The type has trivial default constructor and is trivially copyable, so it's a "trivial class".

Wouldn't making this kind of type as "trivial class" bring trouble? E.g., for things like object lifetime, byte-wise copy equivalence, goto statement allowance etc.

EDIT: The following example of goto allowance is not valid. Thank you for @Casey's comment. Another example of byte-wise copy equivalence is added to replace this one.

Take goto statement allowance as an example, the standards says:

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps87 from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer (8.5).

So for the following code:

class A {
    int& a;
public:
    A(int& aa): a{aa} {}
    A()=default;  // this is necessary otherwise no default constructor will be implicitly declared then the type will not be trivial
};

int i;

int main() {
    static_assert(is_trivial<A>::value, "");
    goto L;
    A a{i};
L:
    return 0;
}

It is well-formed according to the rules because A has a trivial default constructor and a trivial destructor (the assertion passes OK). On the opposite, the code is ill-formed in C++03 (with C++11-only syntax removed, i.e. the line of A()=default;), because A is not a POD in C++03, and C++03 allows goto to cross definition of POD type only.

Take byte-wise copy equivalence as an example, the standard says:

For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied into obj2,41 obj2 shall subsequently hold the same value as obj1.

So memcpy() on trivially copyable type is well-defined:

class A {
    int& a;
public:
    A(int& aa): a{aa} {}
    A()=default;  // this is necessary otherwise no default constructor will be implicitly declared then the type will not be trivial
    void* addr() {return &a;}
};

int i = 0;
int j = 0;

int main() {
    static_assert(is_trivial<A>::value, "");
    A a{i};
    A b{j};
    cout << a.addr() << " " << b.addr() << "\n";
    // a = b;  // this will be ill-formed because the implicitly defaulted copy assignment is defined as deleted
    memcpy(&a, &b, sizeof(A));  // this is well-defined because A is trivial
    cout << a.addr() << " " << b.addr() << "\n";
}

It is well-defined according to the rules because A is a trivial type (the assertion passes OK). The result shows that a reference is made to refer to different objects at different times. On the opposite, the code is undefined in C++03 (with C++11-only syntax removed, i.e. the line of A()=default;), because A is not a POD in C++03, and C++03 allows byte-wise copy equivalence of POD type only.

Was it helpful?

Solution

CWG issue 667 addressed this exact issue with a change that was incorporated into the C++ working draft near N3225. N3225 § 12.1 [class.ctor]/5 states:

A default constructor is trivial if it is neither user-provided nor deleted 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.

This was (obviously) changed before C++11 release. CWG DR 1135 was created to address a Finland national body comment on the C++11 candidate draft:

It should be allowed to explicitly default a non-public special member function on its first declaration. It is very likely that users will want to default protected/private constructors and copy constructors without having to write such defaulting outside the class.

The resolution of this issue removed the "nor deleted" text from 12.1 as well as the sections describing trivial destructors, trivial copy/move constructors, and trivial copy/move assignment operators. I think this change cut too broad a swath, and it was likely not intentional to make your struct A trivial. Indeed, on face value it's ridiculous that this program is ill-formed:

int x = 42;
int y = 13;
A a_x{x};
A a_y{y};
a_x = a_y;

but this program is not, since A is trivially copyable (Clang agrees, GCC does not):

int x = 42;
int y = 13;
A a_x{x};
A a_y{y};
std::memcpy(&a_x, &a_y, sizeof(a_x));

The existence of CWG issue 1496 "Triviality with deleted and missing default constructors" seems to indicate that the committee is aware of the problem (or at least a closely related problem):

A default constructor that is defined as deleted is trivial, according to 12.1 [class.ctor] paragraph 5. This means that, according to 9 [class] paragraph 6, such a class can be trivial. If, however, the class has no default constructor because it has a user-declared constructor, the class is not trivial. Since both cases prevent default construction of the class, it is not clear why there is a difference in triviality between the cases.

although there is no resolution as yet.

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