LWG 2424 discusses the undesirable status of atomics, mutexes and condition variables as trivially copyable in C++14. I appreciate that a fix is already lined up, but std::mutex
Either it was my mistake, or I was misquoted, and I honestly don't recall which.
However, I have this very strongly held advice on the subject:
Do not use
is_trivial
noris_trivially_copyable
! EVER!!!
Instead use one of these:
is_trivially_destructible<T>
is_trivially_default_constructible<T>
is_trivially_copy_constructible<T>
is_trivially_copy_assignable<T>
is_trivially_move_constructible<T>
is_trivially_move_assignable<T>
Rationale:
tldr: See this excellent question and correct answer.
No one (including myself) can remember the definition of is_trivial
and is_trivially_copyable
. And if you do happen to look it up, and then spend 10 minutes analyzing it, it may or may not do what you intuitively think it does. And if you manage to analyze it correctly, the CWG may well change its definition with little or no notice and invalidate your code.
Using is_trivial
and is_trivially_copyable
is playing with fire.
However these:
is_trivially_destructible<T>
is_trivially_default_constructible<T>
is_trivially_copy_constructible<T>
is_trivially_copy_assignable<T>
is_trivially_move_constructible<T>
is_trivially_move_assignable<T>
do exactly what they sound like they do, and are not likely to ever have their definition changed. It may seem overly verbose to have to deal with each of the special members individually. But it will pay off in the stability/reliability of your code. And if you must, package these individual traits up into a custom trait.
Update
For example, clang & gcc compile this program:
#include <type_traits>
template <class T>
void
test()
{
using namespace std;
static_assert(!is_trivial<T>{}, "");
static_assert( is_trivially_copyable<T>{}, "");
static_assert( is_trivially_destructible<T>{}, "");
static_assert( is_destructible<T>{}, "");
static_assert(!is_trivially_default_constructible<T>{}, "");
static_assert(!is_trivially_copy_constructible<T>{}, "");
static_assert( is_trivially_copy_assignable<T>{}, "");
static_assert(!is_trivially_move_constructible<T>{}, "");
static_assert( is_trivially_move_assignable<T>{}, "");
}
struct X
{
X(const X&) = delete;
};
int
main()
{
test<X>();
}
Note that X
is trivially copyable, but not trivially copy constructible. To the best of my knowledge, this is conforming behavior.
VS-2015 currently says that X
is neither trivially copyable nor trivially copy constructible. I believe this is wrong according to the current spec, but it sure matches what my common sense tells me.
If I needed to memcpy
to uninitialized memory, I would trust is_trivially_copy_constructible
over is_trivially_copyable
to assure me that such an operation would be ok. If I wanted to memcpy
to initialized memory, I would check is_trivially_copy_assignable
.
Not all implementations provide a nontrivial destructor for mutex
. See libstdc++ (and assume that __GTHREAD_MUTEX_INIT
has been defined):
// Common base class for std::mutex and std::timed_mutex
class __mutex_base
{
// […]
#ifdef __GTHREAD_MUTEX_INIT
__native_type _M_mutex = __GTHREAD_MUTEX_INIT;
constexpr __mutex_base() noexcept = default;
#else
// […]
~__mutex_base() noexcept { __gthread_mutex_destroy(&_M_mutex); }
#endif
// […]
};
/// The standard mutex type.
class mutex : private __mutex_base
{
// […]
mutex() noexcept = default;
~mutex() = default;
mutex(const mutex&) = delete;
mutex& operator=(const mutex&) = delete;
};
This implementation of mutex
is both standard conforming and trivially copyable (which can be verified via Coliru). Similarly, nothing stops an implementation from keeping condition_variable
trivially destructible (cf. [thread.condition.condvar]/6, although I couldn't find an implementation that does).
The bottom line is that we need clear, normative guarantees, and not clever, subtle interpretations of what condition_variable
does or doesn't have to do (and how it has to accomplish that).
It's important to look at this from a language lawyer perspective.
It's basically impossible for an implementation to implement mutex
, condition variables, and the like in a way that leaves them trivially copyable. At some point, you have to write a destructor, and that destructor is very likely going to have to do non-trivial work.
But that doesn't matter. Why? Because the standard does not explicitly state that such types will not be trivially copyable. And therefore it is, from the perspective of the standard, theoretically possible for such objects to be trivially copyable.
Even though no functional implementation every can be, the point of N4460 is to make it very clear that such types will never be trivially copyable.