LWG 2424 points out that atomic types should not be trivially copyable. During an issue teleconference, Howard Hinnant pointed out that the same issue applies to mutexes and condition variables, and that it would be preferrable to state such things more generally rather than having it on every individual library type that requires non-trivial copyability. It seems that the library specification is faulty in this regard, since such types have deleted copy operations, but those do not render the types non-trivially copyable. It was also mentioned that the core language has been in flux in this area, and the library should have a sane and stable way to specify such things. There are a couple of ways to solve the problem, but there are subtleties involved.
Quoting [class]/6:
A trivially copyable class is a class that: - has no non-trivial copy constructors (12.8), - has no non-trivial move constructors (12.8), - has no non-trivial copy assignment operators (13.5.3, 12.8), - has no non-trivial move assignment operators (13.5.3, 12.8), and - has a trivial destructor (12.4).
A deleted copy constructor is trivial, so the current specification of atomic types renders them trivially copyable.
So, why don't we just say that a deleted special member function is non-trivial? Alas, that would mean the following type becomes non-trivial:
struct X
{
const int val;
};
X has a deleted copy assignment operator. That doesn't mean that X is not trivially copyable. And X has been trivial since the dawn of time, so we must be careful not to break such expectations.
We could solve the problem by stating that explicitly deleted special member functions are non-trivial. Such ideas have been floated before, and several people would apparently find it intuitive. The downside is that in general we try to avoid differences between explicit and implicit default/delete.
The library types could just make the copy operations user-provided. That is, instead of
atomic& operator=(const atomic&) = delete;
the library would need to do
private:
atomic& operator=(const atomic&);
Now the special member function is not trivial, since it's user-provided.
The general downside of the technique is that friends can still try to
call such a special member, and the result of the call is a linker error.
That problem is surmountable, since there are no user-visible friends
of such library types.
The proposed solution for LWG 2424 just states that the types are not trivially copyable. That's a good solution in the sense that it doesn't over-specify how an implementation renders the type non-trivially copyable, but it has the downside that we should at least be aware of how a library implementation does that in terms of the core language, and the current synopses that use deleted special member functions do not work.
An "evil" solution would be to specialize is_trivially_copyable
and is_trivial
for the types that are not desired to be trivially
copyable or trivial. Since it's undefined behaviour to do so in
a C++ program, such a solution would potentially require compiler
help. Such a solution is undesirable for library implementations
that need to work with multiple compilers.
Due to the struct X example, it seems we should tread very carefully with suggestions to change the semantics of deleted special member functions. It seems recommendable to
condition_variable
and all library mutexes and locks,
and replace that with..
It's debatable whether the deleted special member functions need to
be stricken. We want to make sure that some of these types are not copyable
and not movable, and additionally that none of them are trivially copyable.
It's potentially possible to retain the deleted special member functions and
add additional signatures (in the library implementation, not in the
specification) that are user-provided, but I don't find that solution
very clean. It would certainly be useful to hash out compiler bugs
in the implementation of is_trivially_copyable
(I found some compilers
that don't do the right thing with "has no non-trivial.." part of it).