Why are mutexes and condition variables trivially copyable? - c++

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, std::condition variable et al. appear to have non-trivial destructors. For example:
30.4.1.2.1 Class mutex [thread.mutex.class]
namespace std {
class mutex {
public:
constexpr mutex() noexcept;
~mutex(); // user-provided => non-trivial
…
}
}
Shouldn't this disqualify them as trivially copyable?

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 nor is_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.

Related

Should class be not trivially destructible, if standard specifies that it has a destructor?

Consider std::latch [thread.latch.class]:
namespace std {
class latch {
public:
static constexpr ptrdiff_t max() noexcept;
constexpr explicit latch(ptrdiff_t expected);
~latch();
latch(const latch&) = delete;
latch& operator=(const latch&) = delete;
void count_down(ptrdiff_t update = 1);
bool try_wait() const noexcept;
void wait() const;
void arrive_and_wait(ptrdiff_t update = 1);
private:
ptrdiff_t counter; // exposition only
};
}
Note that destructor is present. This means that this should hold:
static_assert(std::is_trivially_destructible_v<std::latch> == false);
However, it is opposite for the implementations (MSVC STL, libstdc++, libc++): https://godbolt.org/z/6s8173zTc
Is this:
Implementation freedom (implementations are correct, is_trivially_destructible_v may be true or false)
Implementation defect in every implementation (implementation should have non-trivial destructor for std::latch)
Standard defect (Standard shouldn't have specified std::latch to have non-trivial destructor)
?
This is implementation freedom. The C++ standard defines the class, the implementation of the class is up, well, to the implementation.
There are some classes where the standard explicitly mandates a trivial destructor. For example, if an existing class is trivially destructible then its std::optional also must be trivially destructible. This needs to be spelled out.
Therefore, unless somewhere there is an explicit statement that the class is or is not trivially constructible, then this is up to the implementation (where possible).
Looking at gcc's header file: it doesn't merely declare, but it explicitly defines the destructor:
~latch() = default;
Based on that the compiler can work out that the whole thing is trivially destructible.

std::optional implemented as union vs char[]/aligned_storage

While reading through GCC's implementation of std::optional I noticed something interesting. I know boost::optional is implemented as follows:
template <typename T>
class optional {
// ...
private:
bool has_value_;
aligned_storage<T, /* ... */> storage_;
}
But then both libstdc++ and libc++ (and Abseil) implement their optional types like this:
template <typename T>
class optional {
// ...
private:
struct empty_byte {};
union {
empty_byte empty_;
T value_;
};
bool has_value_;
}
They look to me as they are functionally identical, but are there any advantages of using one over the other? (Except for the obvious lack of placement new in the latter which is really nice.)
They look to me as they are functionally identical, but are there any advantages of using one over the other? (Except for the obvious lack placement new in the latter which is really nice.)
It's not just "really nice" - it's critical for a really important bit of functionality, namely:
constexpr std::optional<int> o(42);
There are several things you cannot do in a constant expression, and those include new and reinterpret_cast. If you implemented optional with aligned_storage, you would need to use the new to create the object and reinterpret_cast to get it back out, which would prevent optional from being constexpr friendly.
With the union implementation, you don't have this problem, so you can use optional in constexpr programming (even before the fix for trivial copyability that Nicol is talking about, optional was already required to be usable as constexpr).
std::optional cannot be implemented as aligned storage, due to a post-C++17 defect fix. Specifically, std::optional<T> is required to be trivially copyable if T is trivially copyable. A union{empty; T t}; will satisfy this requirement
Internal storage and placement-new/delete usage cannot. Doing a byte copy from a TriviallyCopyable object to storage that does not yet contain an object is not sufficient in the C++ memory model to actually create that object. By contrast, the compiler-generated copy of an engaged union over TriviallyCopyable types will be trivial and will work to create the destination object.
So std::optional must be implemented this way.

Why atomic_flag default constructor leaves state unspecified?

When using std::atomic_flag, one has to be careful to always explicitely initialize it using ATOMIC_FLAG_INIT, which is error-prone. However there is a default constructor... So, is there an objective reason behind having a default constructor leaving the flag in an unspecifiate state ?
This link (posted by dyp in comments), describes that this decision was made because on some architectures a zero-initialized atomic_flag would correspond to a set state, and on some it would correspond to a cleared state. Because of this, it was defined that an atomic_flag that is not explicitly initialized with ATOMIC_FLAG_INIT is initially in an indeterminate state.
This question was already answered by this answer
I had this issue come up with a map that contained a tuple that contained an std::atomic_flag - and putting in explicit initalization easily adds a lot of complicated code.
A workaround solution is to just wrap it in another type, such as this:
struct atomic_flag_ : public std::atomic_flag {
atomic_flag_() : std::atomic_flag{ ATOMIC_FLAG_INIT } {
}
};
or without inheritance:
struct atomic_flag_ {
std::atomic_flag flag{ ATOMIC_FLAG_INIT };
};
Now you don't have to worry about initalization (except in some special corner cases, like static init fiasco).

What algorithms and containers in the C++ 11 STL can go faster if a type is noexcept?

A debate came up at work regarding how much to care about using noexcept. We all know that noexcept doesn't really do a huge amount for the compiler's optimiser except for externally defined code which the compiler otherwise has to assume can throw because it can't know its implementation, so the only real other performance benefit of marking things noexcept is for code which makes use of std::move_if_noexcept<> which it would be assumed would be mostly STL containers and their algorithms.
The assessment would therefore be this: do not use noexcept unless:
extern functions and classes where the implementation of a callable isn't known to the compiler.
Move constructors, move assignment operators and swap for any type which might be contained in a STL container.
Otherwise don't worry about it.
Is this a fair assessment? Are there other places in the STL which generate much more optimal code if something is noexcept? If so, which STL implementation is this and what needs to be marked noexcept for it to work, and what performance benefit results (fewer memory allocations, lower complexity)?
Edit: Made CashCow's suggested change to wording.
Is this a fair assessment?
No... it's unnecessarily fragile during code evolution/maintenance. Consider what happens if you follow your rules for this code...
// awesome_lib.h
void f() noexcept; // out-of-line implementation: { }
// awesome_app.cpp
void c1() noexcept; // out-of-line implementation: { f(); }
// cool_app.cpp
void c2() noexcept; // out-of-line implementation: { f(); }
...then say f() wants to report a new class of issue via exceptions, so it removes noexcept and conditionally throws... unless all the client code calling f() - c1, c2, ... is found and updated, the applications may std::terminate instead of letting the exception propagate to whatever catch clause might otherwise be available. Why would you want that? Yes you could use the noexcept operator to express the noexcept nature of c1 and c2 in terms of f and other called functions, but that's verbose and fragile. It's not like const where compiler errors help you keep things consistent.
It's better to make targeted use of noexcept where it's required by a specific profiling-lead optimisation opportunity.

Passing a class object with trivial copy constructor, but no output?

I tried this program with GCC and Clang, but both output nothing
#include <iostream>
struct A {
A(){}
template<typename T>
A(T &) {
std::cout << "copied!";
}
};
void f(...) { }
int main() {
A a;
f(a);
}
According to my Standards reading, this program should output "copied!". Can anyone tell me whether I am mistaken or whether this is a bug in those two compilers?
It would seem that what you expect is the behavior defined by the standard.
Template functions do not prevent the creation of copy constructors/assignment operators. So template functions don't prevent a class from being considered "trivially copyable". However, they do participate in overload resolution when it comes time to actually copy them, so they can interfere. And since a in this example is a non-const l-value, it better fits the signature A(A&) than it does A(const A&). So it calls the template function.
(Though why you didn't bother to explain all of this in your question eludes me, since you obviously did your research.)
However, considering how small of a corner-case this is, I wouldn't go around relying on this behavior to force trivially copyable classes into not being trivially copied.