The current standards for C++17 (and I've observed similar wording for C++11) have very confusing wording for trivially copyable types. I first stumbled upon this problem with the following code (GCC 5.3.0):
class TrivialClass {};
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??
Making the confusion even worse, I tried checking to see what std::is_trivial
had to say about the matter, only being brought to more confusion.
class TrivialClass {};
std::is_trivial<int volatile>::value; // 1 ??
std::is_trivial<TrivialClass volatile>::value; // 1
Confused, I checked the latest C++17 draft to see if something was amiss, and I found some slightly ambiguous wording which might be the culprit:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.73
cv-unqualified scalar types, trivially copyable class types (Clause 9), arrays of such types, and non-volatile const-qualified versions of these types (3.9.3) are collectively called trivially copyable types.
Here is the information on trivially copyable classes:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.226
A trivially copyable class is a class that:
— (6.1) has no non-trivial copy constructors (12.8),
— (6.2) has no non-trivial move constructors (12.8),
— (6.3) has no non-trivial copy assignment operators (13.5.3, 12.8),
— (6.4) has no non-trivial move assignment operators (13.5.3, 12.8), and
— (6.5) has a trivial destructor (12.4).
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#section.12.8
Constructors:
A copy/move constructor for class X is trivial if it is not user-provided, its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if
— (12.1) class X has no virtual functions (10.3) and no virtual base classes (10.1), and
— (12.2) class X has no non-static data members of volatile-qualified type, and
— (12.3) the constructor selected to copy/move each direct base class subobject is trivial, and
— (12.4) for each non-static data member of X that is of class type (or array thereof), the constructor selected to copy/move that member is trivial;
otherwise the copy/move constructor is non-trivial.
Assignment:
A copy/move assignment operator for class X is trivial if it is not user-provided, its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if
— (25.1) class X has no virtual functions (10.3) and no virtual base classes (10.1), and
— (25.2) class X has no non-static data members of volatile-qualified type, and
— (25.3) the assignment operator selected to copy/move each direct base class subobject is trivial, and
— (25.4) for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is trivial;
otherwise the copy/move assignment operator is non-trivial.
Note: Updated this section with more information. I now believe this to be a bug in GCC. However this alone doesn't answer all my questions.
I could see that maybe it's because TrivialClass has no non-static members, as that would pass the above rules, so I added an int, and it still returns as trivially copyable.
class TrivialClass { int foo; };
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??
The standard states that volatile should be inherited by sub-objects of a volatile object. Meaning TrivialClass volatile
's non-static data member foo
should now be of type int volatile
.
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.76
A volatile object is an object of type volatile T, a subobject of such an object, or a mutable subobject of a const volatile object
We can confirm this is working in GCC via:
std::is_same<decltype(((TrivialClass volatile*)nullptr)->foo), int volatile>::value; // 1! (Expected)
Confused, I then added a volatile to int foo
itself. It still passes, which is obviously a bug!
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68905#c1
class TrivialClass { int volatile foo; };
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??
Moving on, we see that std::is_trivial
is also working as expected:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.73
Scalar types, trivial class types (Clause 9), arrays of such types and cv-qualified versions of these types (3.9.3) are collectively called trivial types.
Okay, so I have a lot of questions here.
- Why does volatile matter for is_trivially_copyable and not is_trivial?
- What's the deal with is_trivially_copyable and object types, is it a bug or an issue with the standard?
- Why does it matter if something is volatile anyways?
Can anyone help me wrap my head around this, I'm really at a loss here.