Why is the copy constructor not trivial just because there is a user defined destructor? [duplicate]
Asked Answered
B

2

7

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

Borrow answered 6/6, 2019 at 9:3 Comment(3)
You'll get different results if you would use = default for your destructor.Rrhagia
cplusplus.github.io/LWG/issue2827Malacology
"Having this question allows anybody to see this is related" The question will still be here if it's a duplicate. It will simply point to another question, which shows that they are related.Big
M
1

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.

Malacology answered 6/6, 2019 at 9:27 Comment(4)
That is the definition of a variable t, t is not being destroyed. The semicolon makes a difference: it is not a temporary expression that creates and destroys a T, it just creates a variable t that is not destroyed there.Borrow
@Borrow cplusplus.github.io/LWG/issue2827Malacology
Because of that last link (excellent find), we can conclude that these implementations are broken because what they consider is not S t(); but S{}, an expression that creates and destroys.Borrow
@Borrow I'd say it is an issue in the wording. It should say that the variable definition known not to cause calls to operation that is not trivial.Malacology
A
1

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.

Adley answered 6/6, 2019 at 9:28 Comment(8)
There does not seem to be a relation between the variable definition t and the destructor. No destructor gets executed in the T t(declval<const T &>()), so, what is the non-trivial operation that drives the move, copy constructors non-trivial?Borrow
@Borrow A variable definition is supposed to "call" the destructor.Adley
That is the definition of a variable t, in a statement (there is a semicolon), the destructor of t is not called in that statement. That is the statement used to specify what are the operations involved to decide triviality, so, your answer is simply not a reasonBorrow
@Borrow That variable definition results in a call to the S destructor. cplusplus.github.io/LWG/issue2827Malacology
@Peter The destructor can only be called if the constructor succeeded. In T t(declval<Args>()...); if T constructor throws no T destructor is called, if the constructor succeeds, no destructor is called either.Goodrow
@Borrow Apparently you are confused here. That's because this is a bit of confusing. The variable definition is considered to be responsible call to the destructor even though the call does not appear to happen at the place of the variable definition.Adley
@Peter And what if a T is a member of a class/struct It is not a variable definition then.Malacology
@Peter If T is a member of T than sizeof(T) is infinite because each T must have a unique address.Goodrow

© 2022 - 2024 — McMap. All rights reserved.