Example code could be found below or on godbolt. Say we have 4 classes:
S<T>
: holding a data member.SCtor<T>
: holding a data member and has a template constructor.SCtorMutable<T>
: holding a mutable data member and has a template constructor.SCtorDefault<T>
: holding a member, has a template constructor, has defaulted copy/move constructors and defaulted copy/move assignment operators.
All compilers agree that these 4 classes are trivially copyable.
If there is a simple wrapper class W<T>
holding any of the above class as a data member. The wrapper class W<S...<T>>
is still trivially copyable.
If there is another wrapper class WMutable<T>
holding any of the above class as a mutable data member.
- MSVC still believes
WMutable<S...<T>>
is trivially copyable. - clang believes
WMutable<S<T>>
is trivially copyable.WMutable<SCtor...<T>>
is not trivially copy constructible therefore not trivially copyable. - gcc believes
WMutable<S<T>>
is trivially copyable.WMutable<SCtor...<T>>
is not trivially copy constructible BUT trivially copyable.
Should WMutable<T>
be trivially copyable?
#include <type_traits>
#include <utility>
template<typename T>
struct S {
T m_t;
};
template<typename T>
struct SCtor {
T m_t;
template<typename... U>
SCtor(U&&... u): m_t(std::forward<U>(u)...) {}
};
template<typename T>
struct SCtorMutable {
mutable T m_t;
template<typename... U>
SCtorMutable(U&&... u): m_t(std::forward<U>(u)...) {}
};
template<typename T>
struct SCtorDefault {
T m_t;
template<typename... U>
SCtorDefault(U&&... u): m_t(std::forward<U>(u)...) {}
SCtorDefault(SCtorDefault const&) = default;
SCtorDefault(SCtorDefault&&) = default;
SCtorDefault& operator=(SCtorDefault const&) = default;
SCtorDefault& operator=(SCtorDefault&&) = default;
};
template<typename T>
struct W {
T m_t;
};
template<typename T>
struct WMutable {
mutable T m_t;
};
static_assert(std::is_trivially_copyable<S<int>>::value);
static_assert(std::is_trivially_copy_constructible<S<int>>::value);
static_assert(std::is_trivially_move_constructible<S<int>>::value);
static_assert(std::is_trivially_copy_assignable<S<int>>::value);
static_assert(std::is_trivially_move_assignable<S<int>>::value);
static_assert(std::is_trivially_copyable<SCtor<int>>::value);
static_assert(std::is_trivially_copy_constructible<SCtor<int>>::value);
static_assert(std::is_trivially_move_constructible<SCtor<int>>::value);
static_assert(std::is_trivially_copy_assignable<SCtor<int>>::value);
static_assert(std::is_trivially_move_assignable<SCtor<int>>::value);
static_assert(std::is_trivially_copyable<SCtorMutable<int>>::value);
static_assert(std::is_trivially_copy_constructible<SCtorMutable<int>>::value);
static_assert(std::is_trivially_move_constructible<SCtorMutable<int>>::value);
static_assert(std::is_trivially_copy_assignable<SCtorMutable<int>>::value);
static_assert(std::is_trivially_move_assignable<SCtorMutable<int>>::value);
static_assert(std::is_trivially_copyable<SCtorDefault<int>>::value);
static_assert(std::is_trivially_copy_constructible<SCtorDefault<int>>::value);
static_assert(std::is_trivially_move_constructible<SCtorDefault<int>>::value);
static_assert(std::is_trivially_copy_assignable<SCtorDefault<int>>::value);
static_assert(std::is_trivially_move_assignable<SCtorDefault<int>>::value);
static_assert(std::is_trivially_copyable<W<S<int>>>::value);
static_assert(std::is_trivially_copy_constructible<W<S<int>>>::value);
static_assert(std::is_trivially_move_constructible<W<S<int>>>::value);
static_assert(std::is_trivially_copy_assignable<W<S<int>>>::value);
static_assert(std::is_trivially_move_assignable<W<S<int>>>::value);
static_assert(std::is_trivially_copyable<W<SCtor<int>>>::value);
static_assert(std::is_trivially_copy_constructible<W<SCtor<int>>>::value);
static_assert(std::is_trivially_move_constructible<W<SCtor<int>>>::value);
static_assert(std::is_trivially_copy_assignable<W<SCtor<int>>>::value);
static_assert(std::is_trivially_move_assignable<W<SCtor<int>>>::value);
static_assert(std::is_trivially_copyable<W<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_copy_constructible<W<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_move_constructible<W<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_copy_assignable<W<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_move_assignable<W<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_copyable<W<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_copy_constructible<W<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_move_constructible<W<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_copy_assignable<W<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_move_assignable<W<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_copyable<WMutable<S<int>>>::value);
static_assert(std::is_trivially_copy_constructible<WMutable<S<int>>>::value);
static_assert(std::is_trivially_move_constructible<WMutable<S<int>>>::value);
static_assert(std::is_trivially_copy_assignable<WMutable<S<int>>>::value);
static_assert(std::is_trivially_move_assignable<WMutable<S<int>>>::value);
static_assert(std::is_trivially_copyable<WMutable<SCtor<int>>>::value); // error with clang
static_assert(std::is_trivially_copy_constructible<WMutable<SCtor<int>>>::value); // error with clang/gcc
static_assert(std::is_trivially_move_constructible<WMutable<SCtor<int>>>::value);
static_assert(std::is_trivially_copy_assignable<WMutable<SCtor<int>>>::value);
static_assert(std::is_trivially_move_assignable<WMutable<SCtor<int>>>::value);
static_assert(std::is_trivially_copyable<WMutable<SCtorMutable<int>>>::value); // error with clang
static_assert(std::is_trivially_copy_constructible<WMutable<SCtorMutable<int>>>::value); // error with clang/gcc
static_assert(std::is_trivially_move_constructible<WMutable<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_copy_assignable<WMutable<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_move_assignable<WMutable<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_copyable<WMutable<SCtorDefault<int>>>::value); // error with clang
static_assert(std::is_trivially_copy_constructible<WMutable<SCtorDefault<int>>>::value); // error with clang/gcc
static_assert(std::is_trivially_move_constructible<WMutable<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_copy_assignable<WMutable<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_move_assignable<WMutable<SCtorDefault<int>>>::value);
Clang is the only correct of the three compilers. The short answer is that adding
mutable
to the data member results in the non-trivial variadic constructor winning in overload resolution over the trivial, implicitly defined copy constructor. This happens in the copy constructor ofWMutable
, soWMutable
is not trivially copyable.The Long Answer
What
mutable
generally does is:- https://eel.is/c++draft/basic.type.qualifier#1
This means that our
SCtor<int>
data member is notconst
, which impacts overload resolution. Let's consider what the typeconst WMutable<SCtor<int>>>
expands to:An implicitly defined or explicitly defaulted copy constructor copies each member. Copying a member may not necessarily use a copy constructor:
- https://eel.is/c++draft/class.copy.ctor#14
This means that we get something along the lines of:
m_t
will be initialized to an argument of type (lvalue)SCtor<int>
, and there are two constructors that this can call:Constructor (2) wins in overload resolution, because the conversion sequence from (lvalue)
SCtor<int>
toSCtor<int>
& is shorter than toconst SCtoer<int>&
.As a result, the type
WMutable<SCtor<int>>
(and other specializations ofWMutable
in your example) is not trivially copyable, because it violates the requirement:- https://eel.is/c++draft/class.prop#1
The copy constructor of
WMutable<SCtoer<int>>
is not trivial, and so the the class is not trivially copyable, and not trivially copy-constructible.GCC and MSVC Bugs
GCC and MSVC must falsely restrict the overload set to only copy constructors, not additional constructors that can be used for copying members. The shortest way to reproduce this bug is:
See live example on Compiler Explorer
However, for this more simple example, GCC and Clang agree. Only MSVC is non-compliant (unchanged by
/permissive-
).