Why is the copy constructor not trivial just because there is a user defined destructor?

Viewed 1168

The following excerpt compiles in Clang-libstdc++ or Clang-libc++, GCC, many of their versions and all three versions of the language since 11 (14 & 17):

#include <type_traits>

struct HasUserDefinedDestructor {
    ~HasUserDefinedDestructor() {}
};

using HUDD = HasUserDefinedDestructor;

static_assert(not std::is_trivially_move_constructible<HUDD>::value, "");
static_assert(not std::is_trivially_copy_constructible<HUDD>::value, "");

This surprises me, since a copy only requires trivial operations.

Is this an error in the compilers/libraries or does the standard say somewhere that having a user defined destructor makes the copy and move constructors not trivial?

Edit: Why this is not a repeat of default construction question: Given the comments we know "noexceptness" and triviality of constructors are affected by the noexceptness and triviality of the destructor, but before knowing that all of these traits are related the questions are different. Having this question allows anybody to see this is related

2 Answers

is_trivially_constructible is defined as follows (bold is mine):

is_­constructible_­v<T,Args...> is true and the variable definition for is_­constructible, as defined below, is known to call no operation that is not trivial

«Defined below» is [meta.unary.op]/8:

The predicate condition for a template specialization is_­constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:

T t(declval<Args>()...);

So yes, is_trivially_[copy|move]_constructible_v is false when the destructor is not trivial.

Per [dcl.fct.def.default]/5:

[...] A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. [...]

Therefore, given

struct HasUserDefinedDestructor {
    ~HasUserDefinedDestructor() {}
};

HasUserDefinedDestructor has a user-provided destructor.

Per [class.dtor]/6:

A destructor is trivial if it is not user-provided and if: [...]

Otherwise, the destructor is non-trivial.

Therefore, HasUserDefinedDestructor has a non-trivial destructor.

Per [meta.unary.prop]:

template <class T, class... Args>
struct is_­trivially_constructible;

Condition: is_­constructible_­v<T, Args...> is true and the variable definition for is_­constructible, as defined below, is known to call no operation that is not trivial ([basic.types], [special]).

Pre-condition: T and all types in the parameter pack Args shall be a complete type, cv void, or an array of unknown bound.

template <class T>
struct is_­trivially_copy_­constructible;

Condition: For a referenceable type T, the same result as is_­trivially_­constructible_­v<T, const T&>, otherwise false.

Pre-condition: T shall be a complete type, cv void, or an array of unknown bound.

template <class T>
struct is_­trivially_­move_­constructible;

Condition: For a referenceable type T, the same result as is_­trivially_­constructible_­v<T, T&&>, otherwise false.

Pre-condition: T shall be a complete type, cv void, or an array of unknown bound.

Per [meta.unary.prop]/8:

The predicate condition for a template specialization is_­constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:

T t(declval<Args>()...);

[ Note: These tokens are never interpreted as a function declaration. — end note ] Access checking is performed as if in a context unrelated to T and any of the Args. Only the validity of the immediate context of the variable initialization is considered. [ Note: The evaluation of the initialization can result in side effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such side effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]

The variable definition is supposed to "call" the destructor even though it seems that the destructor is not called at the place of the defintion. Therefore, std::is_trivially_move_constructible<HUDD>::value is false, as is std::is_trivially_copy_constructible<HUDD>::value.

Related