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 http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.73
cv 未限定标量类型、普通可复制类类型(第 9 条)、此类类型的数组以及这些类型的非易失性 const 限定版本 (3.9.3) 统称为普通可复制类型。
以下是有关可简单复制的类的信息:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.226 http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.226
普通可复制类是这样的类:
— (6.1) 没有重要的复制构造函数 (12.8),
— (6.2) 没有重要的移动构造函数 (12.8),
— (6.3) 没有重要的复制赋值运算符 (13.5.3, 12.8),
— (6.4) 没有非平凡的移动赋值运算符 (13.5.3, 12.8),并且
— (6.5) 有一个简单的析构函数 (12.4)。
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#section.12.8 http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#section.12.8
构造函数:
如果类 X 的复制/移动构造函数不是用户提供的,则它是微不足道的,其参数类型列表相当于隐式声明的参数类型列表,并且如果
— (12.1) 类 X 没有虚函数 (10.3) 和虚基类 (10.1),并且
— (12.2) X 类有没有 volatile 限定类型的非静态数据成员, and
— (12.3) 选择复制/移动每个直接基类子对象的构造函数很简单,并且
— (12.4) 对于 X 的类类型(或其数组)的每个非静态数据成员,选择复制/移动该成员的构造函数是微不足道的;
否则复制/移动构造函数是不平凡的。
任务:
如果不是用户提供的,则类 X 的复制/移动赋值运算符是微不足道的,其参数类型列表相当于隐式声明的参数类型列表,并且如果
— (25.1) 类 X 没有虚函数 (10.3) 和虚基类 (10.1),并且
— (25.2) X 类有没有 volatile 限定类型的非静态数据成员, and
— (25.3) 选择用于复制/移动每个直接基类子对象的赋值运算符是微不足道的,并且
— (25.4) 对于 X 的类类型(或其数组)的每个非静态数据成员,选择复制/移动该成员的赋值运算符是微不足道的;
否则复制/移动赋值运算符是不平凡的。
注意:使用更多信息更新了本节。我现在相信这是 GCC 中的一个错误。然而,仅此并不能回答我所有的问题。
我可以看到,也许是因为 TrivialClass 没有非静态成员,因为这会通过上述规则,所以我添加了一个 int,它仍然返回为普通可复制的。
class TrivialClass { int foo; };
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??
该标准规定,易失性应该由易失性对象的子对象继承。意义TrivialClass volatile
的非静态数据成员foo
现在应该是类型int volatile
.
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.76 http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.76
易失性对象是 易失性 T 类型的对象、此类对象的子对象或 const 易失性对象的可变子对象
我们可以通过以下方式确认这在 GCC 中有效:
std::is_same<decltype(((TrivialClass volatile*)nullptr)->foo), int volatile>::value; // 1! (Expected)
很困惑,然后我添加了一个 volatile 到int foo
本身。还是过去了,这明显是bug啊!
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68905#c1 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 http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.73
标量类型、平凡类类型(第 9 条)、此类类型的数组以及这些类型的 cv 限定版本 (3.9.3) 统称为平凡类型。
好吧,我这里有很多问题。
- 为什么 volatile 对 is_trivially_copyable 重要而不对 is_trivial 重要?
- is_trivially_copyable 和对象类型有什么关系,是错误还是标准问题?
- 无论如何,如果某些东西是不稳定的,那为什么很重要呢?
谁能帮我解决这个问题,我真的很茫然。