运行这个简单的程序时,根据编译器的不同,会观察到不同的行为。
当由 GCC 11.2 编译时,它会打印
true
;当由 MSVC 19.29.30137 编译时,它会打印 false
(两者都是今天的最新版本)。
#include <type_traits>
#include <iostream>
struct S {
int a;
S() = delete;
S(S const &) = delete;
S(S &&) = delete;
S &operator=(S const &) = delete;
S &operator=(S &&) = delete;
~S() = delete;
};
int main() {
std::cout << std::boolalpha;
std::cout << std::is_trivially_copyable_v<S>;
}
相关引用(来自最新的C++23工作草案N4901):
给定 20.15.5.4 [meta.unary.prop],如果 T 是 6.8.1/9 [basic.types.general] 中定义的
std::is_trivially_copyable_v<T>
,则 trivially copyable type
被定义为 true:
算术类型(6.8.2)、枚举类型、指针类型、成员指针类型(6.8.3)、std::nullptr_t、 这些类型的 cv 限定 (6.8.4) 版本统称为标量类型。标量类型,简单地说 可复制的类类型 (11.2)、此类类型的数组以及这些类型的 cv 限定版本统称为 简单可复制的类型。
其中
trivially copyable class types
定义于 11.2/1 [class.prop]:
1 普通可复制类是一个类:
— 至少有一个合格的复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符 (11.4.4、11.4.5.3、11.4.6),
—其中每个符合条件的复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都是微不足道的,并且
— 有一个简单的、未删除的析构函数 (11.4.7)。
符合条件(11.4.4 [特殊]):
1 默认构造函数 (11.4.5.2)、复制构造函数、移动构造函数 (11.4.5.3)、复制赋值运算符、 移动赋值运算符 (11.4.6) 和预期析构函数 (11.4.7) 是特殊成员函数。
6 符合条件的特殊成员函数是指满足以下条件的特殊成员函数:
— 该功能未删除,
— 满足相关约束 (13.5)(如果有),并且
对于这些函数(如 11.4.5.3/11 [class.copy.ctor]、11.4.6/9 [class.copy.assign]、11.4.7/8 [class.dtor] 中定义),—没有同类特殊成员函数受到更多限制
trivial
通常意味着:
- 该功能不是用户提供的。
- 课堂上没有任何虚拟的东西
- 每个非静态数据成员都有相关的琐碎功能
根据 9.5.2/5 [dcl.fct.def.default],所提供程序中删除的函数不是用户提供的:
...如果函数是用户声明的,则该函数是用户提供的 并且在第一次声明时没有明确默认或删除。 ...
如果我的理解是正确的,
struct S
有deleted
special member functions
,使其成为非eligible
,这不符合trivially copyable class type
和trivially copyable type
的要求。因此,符合 MSVC 的行为。这是正确的吗?
S
在 C++11 到 C++23 标准模式中是可以轻松复制的。 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 #96288 和 LLVM bug #38398。