为什么 is_trivially_copyable_v 在 GCC 和 MSVC 中不同?

La *_*ura 14 c++ type-traits language-lawyer c++20

运行这个简单的程序时,根据编译器的不同,会观察到不同的行为。

\n

true在由 GCC 11.2 编译时以及false由带有 的 MSVC 19.29.30137 编译时打印(两者都是今天的最新版本)。

\n
#include <type_traits>\n#include <iostream>\n\nstruct S {\n    int a;\n    \n    S()                     = delete;\n    S(S const &)            = delete;\n    S(S &&)                 = delete;\n    S &operator=(S const &) = delete;\n    S &operator=(S &&)      = delete;\n    ~S()                    = delete;\n};\n\nint main() {\n    std::cout << std::boolalpha;\n    std::cout << std::is_trivially_copyable_v<S>;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

相关引用(来自最新的C++23工作草案N4901):

\n

给定 20.15.5.4 [meta.unary.prop],如果 T 是如 6.8.1/9 [basic.types.general] 中定义的std::is_trivially_copyable_v<T>a,则定义为 true :trivially copyable type

\n
\n

算术类型 (6.8.2)、枚举类型、指针类型、指向成员的指针类型 (6.8.3)、std::nullptr_t\n 和这些类型的 cv 限定版本 (6.8.4) 统称为标量类型。标量类型、普通可复制类类型 (11.2)、此类类型的数组以及这些类型的 cv 限定版本统称为普通可复制类型。

\n
\n

其中trivially copyable class types11.2/1 [class.prop] 中定义:

\n
\n

1 普通可复制类是这样的类:

\n
\n
\n

\xe2\x80\x94 至少有一个合格的复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符 (11.4.4、11.4.5.3、11.4.6),

\n
\n
\n

\xe2\x80\x94 其中每个合格的复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都是微不足道的,并且

\n
\n
\n

\xe2\x80\x94 有一个简单的、未删除的析构函数(11.4.7)。

\n
\n

符合条件(11.4.4 [特殊]):

\n
\n

1 默认构造函数 (11.4.5.2)、复制构造函数、移动构造函数 (11.4.5.3)、复制赋值运算符、\n移动赋值运算符 (11.4.6) 和预期析构函数 (11.4.7) 是特殊成员函数。

\n

6 符合条件的特殊成员函数是指满足以下条件的特殊成员函数:

\n
\n
\n

\xe2\x80\x94 函数没有被删除,

\n
\n
\n

\xe2\x80\x94 满足相关约束(13.5)(如果有),并且

\n
\n
\n

\xe2\x80\x94 没有同类特殊成员函数比较受约束

\n
\n

trivial对于这些函数(如 11.4.5.3/11 [class.copy.ctor]、11.4.6/9 [class.copy.assign]、11.4.7/8 [class.dtor] 中定义)通常意味着:

\n
\n
    \n
  • 该功能不是用户提供的。
  • \n
  • 班级里没有什么虚拟的东西
  • \n
  • 每个非静态数据成员都有相关的简单函数
  • \n
\n
\n

根据 9.5.2/5 [dcl.fct.def.default],所提供程序中删除的函数不是用户提供的:

\n
\n

...如果函数是用户声明的,并且在第一次声明时未显式默认或删除,则该函数是用户提供的。...

\n
\n

如果我的理解是正确的,struct S则将deleted special member functions它们设为非eligible,这不满足 atrivially copyable class type和的要求trivially copyable type。因此,符合 MSVC 的行为。它是否正确?

\n

Jef*_*ett 7

GCC 和 Clang 报告可以在 C++11 到 C++23 标准模式中S轻松复制。MSVC 报告说,在 C++14 到 C++20 标准模式中S不可简单复制

N3337 (~ C++11) 和N4140 (~ C++14) 说:

普通可复制类是这样的类:

  • 没有不平凡的复制构造函数,
  • 没有不平凡的移动构造函数,
  • 没有不平凡的复制赋值运算符,
  • 没有重要的移动赋值运算符,并且
  • 有一个简单的析构函数。

根据这个定义,S 可以简单复制的。

N4659 (~ C++17) 说:

一个普通可复制的类是一个类:

  • 其中每个复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符要么被删除,要么变得微不足道,
  • 至少有一个未删除的复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符,并且
  • 有一个简单的、未删除的析构函数

根据这个定义,S不是简单可复制的。

N4860(~ C++20)说:

一个普通可复制的类是一个类:

  • 至少有一个合格的复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符,
  • 其中每个合格的复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都是微不足道的,并且
  • 它有一个简单的、未删除的析构函数。

根据这个定义,S不是简单可复制的。

因此,正如发布的那样,S它在 C++11 和 C++14 中是可以复制的,但在 C++17 和 C++20 中则不行。

该更改是从2016 年 2 月的DR 1734开始采用的。实施者通常将 DR 视为按照惯例适用于所有先前的语言标准。因此,根据 C++11 和 C++14 的已发布标准,S是可简单复制的,并且按照惯例,较新的编译器版本可能会选择S在 C++11 和 C++14 模式中将其视为不可简单复制。因此,所有编译器都可以说对于 C++11 和 C++14 都是正确的。

对于 C++17 及更高版本,S显然不可简单复制,因此 GCC 和 Clang 是不正确的。这是GCC bug #96288LLVM bug #39050