std::is_trivially_copyable - 为什么可变标量类型不可复制?

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

C++17 的当前标准(我观察到 C++11 的类似措辞)对于普通可复制类型的措辞非常混乱。我首先使用以下代码 (GCC 5.3.0) 偶然发现了这个问题:

class TrivialClass {};
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??

让混乱变得更糟的是,我试图查看 std::is_trivial 对此事的看法,结果却变得更加混乱。

class TrivialClass {};
std::is_trivial<int volatile>::value; // 1 ??
std::is_trivial<TrivialClass volatile>::value; // 1

困惑,我检查了最新的 C++17 草案,看看是否有什么不对劲,我发现了一些稍微模棱两可的措辞,这可能是罪魁祸首:

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.

这里是简单复制的信息类:

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

构造函数:

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.

赋值:

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.

注意:使用更多信息更新了此部分。我现在相信这是 GCC 中的一个错误。然而,仅此一项并不能回答我所有的问题。

我看出来了,可能是因为 TrivialClass 没有非静态成员,因为它会通过上述规则,所以我添加了一个 int,它仍然 returns 可平凡复制。

class TrivialClass { int foo; };
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??

标准规定volatile应该由volatile对象的子对象继承。意思是 TrivialClass volatile 的非静态数据成员 foo 现在应该是 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

我们可以通过以下方式确认这在 GCC 中有效:

std::is_same<decltype(((TrivialClass volatile*)nullptr)->foo), int volatile>::value; // 1! (Expected)

很困惑,然后我给 int foo 本身添加了一个 volatile。还是通过了,明显是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 ??

继续,我们看到 std::is_trivial 也按预期工作:

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.

好的,所以我这里有很多问题。

谁能帮我解决这个问题,我真的很茫然。

显然这是修复标准缺陷的方式,但您不是唯一对此感到困惑的人。

来自http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.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.)

根据问题描述(自 2004 年 12 月 30 日起):

  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.