std::is_trivially_copyable - Why are volatile scalar types not trivially copyable?

Viewed 1161

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.

1 Answers
Related