【问题标题】:Why is is_trivially_copyable_v different in GCC and MSVC?为什么 GCC 和 MSVC 中的 is_trivially_copyable_v 不同?
【发布时间】:2022-01-12 22:54:42
【问题描述】:

在运行这个简单的程序时,会观察到不同的行为,具体取决于编译器。

当使用 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&lt;T&gt; 被定义为真:

算术类型(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)得到满足,并且

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

trivial 用于这些函数(定义在 11.4.5.3/11 [class.copy.ctor]、11.4.6/9 [class.copy.assign]、11.4.7/8 [class.dtor])一般是指:

  • 该功能不是用户提供的。
  • 类没有虚拟的东西
  • 每个非静态数据成员都有相关的琐碎功能

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

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

如果我的理解是正确的,struct Sdeleted special member functions 使它们非eligible,这不符合trivially copyable class typetrivially copyable type 的要求。因此,符合行为是 MSVC 的。这是正确的吗?

【问题讨论】:

标签: c++ language-lawyer c++20 typetraits


【解决方案1】:

GCC 和 Clang 报告 S is trivially copyable 在 C++11 到 C++23 标准模式中。 MSVC 报告 S is not trivially copyable 在 C++14 到 C++20 标准模式下。

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

【讨论】:

    猜你喜欢
    • 2021-03-09
    • 2021-03-31
    • 1970-01-01
    • 2020-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多