25

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.

Community
  • 1
  • 1
TReed0803
  • 585
  • 3
  • 9
  • Note that the standard does not appear to say that the class itself has to be cv-unqualified... but it doesn't explicitly allow volatile classes either. – Kevin Mar 19 '16 at 05:19
  • But it still states clearly that if there are volatile non-static data members, the object itself should not be trivially copyable - is this qualifier not inherited to sub-objects of a volatile object? That's why I wonder whether or not an empty object would make sense to pass, but question why a volatile object with a non-static data member also passes. – TReed0803 Mar 19 '16 at 05:21
  • It is not immediately obvious to me what a volatile class even means. Volatile is usually used on scalar types to indicate things like memory-mapped I/O and other weird stuff that might change out from under your program. I'm not aware of any use case where you put a whole class instance into volatile memory. – Kevin Mar 19 '16 at 05:23
  • @Kevin: That happens, for example, when you're doing mapped I/O and the device you're accessing requires a non-trivial structure (not "trivial" in the C++ sense, BTW). This means that you may use `volatile SomeRegisterStructure` there, for example. – 3442 Mar 19 '16 at 05:25
  • @KemyLand: I would be inclined to describe that by making the individual instance variables volatile. It's probably equivalent for POD-types, but I'm pretty sure you should not have a volatile non-POD type anyway (Is the vtable volatile? The RTTI?). – Kevin Mar 19 '16 at 05:27
  • @Kevin The standard is not clear on those aspects, but I do believe it would make a sub-object 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." With this information, I *would* consider an empty volatile object trivially copyable, but any volatile object with a non-static data member as NOT trivially copyable because of the above. – TReed0803 Mar 19 '16 at 05:31
  • 4
    Volatile is a historical wart. It's not even recommended [in kernel land](https://www.kernel.org/doc/Documentation/volatile-considered-harmful.txt). The fact is that `volatile`'s sole purpose is to forbid *optimizations*, which is *never* good. Use locks instead. – 3442 Mar 19 '16 at 05:37
  • @KemyLand Unfortunately this isn't a question of my implementation for what I'm doing. I really never use volatile, which is why when I happened upon this when playing with type_traits I was confused. I'm making a reflection system, and I want my output to be able to match that of type_traits. It's dynamically inferred by type properties. But if the standard and the compilers don't agree, I can't match output. I found this unit testing my reflection system. – TReed0803 Mar 19 '16 at 05:47
  • @kemyland: volatile is still needed for multithreading – Daniel Mar 19 '16 at 16:21
  • 1
    @Dani: See the article I linked. Basically, you should be using condition variables and locks for that. `volatile` is just harmful for optimizations. Also, see [this excellent post on the topic](http://stackoverflow.com/a/2485177/5249858). – 3442 Mar 19 '16 at 18:12
  • also see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63959 – Hayri Uğur Koltuk Apr 11 '16 at 11:29
  • @KemyLand volatile never meant to be used for thread sync. It's for accessing MMIO. The referred kernel doc generally discourages it, because the kernel has more sophisticated facilities for this purpose. Without such facilities using volatile for MMIO is still the way to go. Or am I missing something? – Gyorgy Szekely Apr 14 '16 at 14:10
  • @3442 "_Volatile is a historical wart_" BS. Volatile is essential. – curiousguy Jul 24 '18 at 22:03

1 Answers1

6

Apparently it's the way a defect in the standard was fixed, but you're not the only one confused about it.

From https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2094:

  1. Trivial copy/move constructor for class with volatile member

Section: 12.8 [class.copy] Status: open Submitter: Daveed Vandevoorde Date: 2015-03-06

The resolution of issue 496 included the addition of 12.8 [class.copy] paragraph 25.2, making a class's copy/move constructor non-trivial if it has a non-static data member of volatile-qualified type. This change breaks the IA-64 ABI, so it has been requested that CWG reconsider this aspect of the resolution.

On a related note, the resolution of issue 496 also changed 3.9 [basic.types] paragraph 9, which makes volatile-qualified scalar types “trivial” but not “trivially copyable.” It is not clear why there is a distinction made here; the only actual use of “trivial type” in the Standard appears to be in the description of qsort, which should probably use “trivially copyable.” (See also issue 1746.)

From the description of issue (from 30.12.2004):

  1. Is a volatile-qualified type really a POD? :

However in 3.9 [basic.types] paragraph 3, the standard makes it clear that PODs can be copied “as if” they were a collection of bytes by memcpy:

For any POD type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the value of obj1 is copied into obj2, using the std::memcpy library function, obj2 shall subsequently hold the same value as obj1. The problem with this is that a volatile qualified type may need to be copied in a specific way (by copying using only atomic operations on multithreaded platforms, for example) in order to avoid the “memory tearing” that may occur with a byte-by-byte copy.

I realise that the standard says very little about volatile qualified types, and nothing at all (yet) about multithreaded platforms, but nonetheless this is a real issue, for the following reason:

The forthcoming TR1 will define a series of traits that provide information about the properties of a type, including whether a type is a POD and/or has trivial construct/copy/assign operations. Libraries can use this information to optimise their code as appropriate, for example an array of type T might be copied with a memcpy rather than an element-by-element copy if T is a POD. This was one of the main motivations behind the type traits chapter of the TR1. However it's not clear how volatile types (or POD's which have a volatile type as a member) should be handled in these cases.Notes from the April, 2005 meeting:

It is not clear whether the volatile qualifier actually guarantees atomicity in this way. Also, the work on the memory model for multithreading being done by the Evolution Working Group seems at this point likely to specify additional semantics for volatile data, and that work would need to be considered before resolving this issue.

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
Tomasz Lewowski
  • 1,935
  • 21
  • 29
  • Accepting this as the answer. It's unfortunate to hear that good reason was not given for why IA64 ABI compatibility breaks when such types are not allowed to be volatile. I'm sure it's for good reason, it just makes the standard do something that I absolutely wouldn't expect. However, it sounds very wishy-washy anyways; volatile is just such a weird special case of C++. – TReed0803 May 02 '16 at 05:08
  • whoops, missed a big part of answer earlier, sorry for that (added it now). Apparently it seems that it wasn't clear what's the relation volatile vs atomic – Tomasz Lewowski May 02 '16 at 09:39