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

14

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

使用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]中定义的trivially copyable type,则std::is_trivially_copyable_v<T>被定义为true:

算术类型(6.8.2),枚举类型,指针类型,成员指针类型(6.8.3),std::nullptr_t和这些类型的cv限定版本被统称为标量类型。标量类型,trivially copyable类类型(11.2),这些类型的数组以及这些类型的cv限定版本被统称为trivially copyable类型。

其中trivially copyable类类型在11.2/1 [class.prop]中定义:

1 一个trivially copyable类是一个类:

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

— 每个合格的复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都是trivial的,且

— 有一个trivial的、未删除的析构函数(11.4.7)。

合格的(11.4.4 [special]):

1 默认构造函数(11.4.5.2),复制构造函数,移动构造函数(11.4.5.3),复制赋值运算符,移动赋值运算符(11.4.6)和潜在析构函数(11.4.7)都是特殊成员函数。

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

— 函数没有被删除,

— 如果有的话,与之相关的约束(13.5)被满足,且

— 同类的特殊成员函数没有更多的约束。

这些函数的trivial(如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 Sdeleted special member functions,使它们不符合 trivially copyable class typetrivially copyable type 的要求。因此,符合要求的行为是 MSVC 的。这样说对吗?

编译器比较。参考此链接。GCC和Clang都将S视为C++17及更高版本中的平凡可复制类型,但在C++14及以下版本中不是。 - Brian61354270
这些编译器已经支持C++23了吗? - Eljay
2
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96288 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98936 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1734 - Jeff Garrett
1
@JeffGarrett,为什么不从缺陷中提取相关信息并发布为答案呢? - SergeyA
1
@SergeyA,时间到了。 :) 我现在已经发布了一个。 - Jeff Garrett
显示剩余3条评论
1个回答

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不是平凡可复制的。
因此,按照发布的标准,在C++11和C++14中,S是平凡可复制的,但在C++17和C++20中不是。
该更改是从2016年2月的DR 1734中采用的。实现者通常按惯例将DR视为适用于所有先前的语言标准。因此,根据C++11和C++14的发布标准,S是平凡可复制的,并且按照惯例,更新的编译器版本可能会选择在C++11和C++14模式下将S视为不是平凡可复制的。因此,可以说所有编译器对于C++11和C++14都是正确的。
对于C++17及以上版本,S显然不是平凡可复制的,所以GCC和Clang都是错误的。这是GCC bug #96288LLVM bug #39050

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接