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

Why is is_trivially_copyable_v different in GCC and MSVC?

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

它在 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> 被定义为真]:

Arithmetic types (6.8.2), enumeration types, pointer types, pointer-to-member types (6.8.3), std::nullptr_t, and cv-qualified (6.8.4) versions of these types are collectively called scalar types. Scalar types, trivially copyable class types (11.2), arrays of such types, and cv-qualified versions of these types are collectively called trivially copyable types.

其中 trivially copyable class types 定义为 11.2/1 [class.prop]:

1 A trivially copyable class is a class:

— that has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator (11.4.4, 11.4.5.3, 11.4.6),

— where each eligible copy constructor, move constructor, copy assignment operator, and move assignment operator is trivial, and

— that has a trivial, non-deleted destructor (11.4.7).

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

1 Default constructors (11.4.5.2), copy constructors, move constructors (11.4.5.3), copy assignment operators, move assignment operators (11.4.6), and prospective destructors (11.4.7) are special member functions.

6 An eligible special member function is a special member function for which:

— the function is not deleted,

— the associated constraints (13.5), if any, are satisfied, and

— no special member function of the same kind is more constrained

trivial 用于这些函数(如 11.4.5.3/11 [class.copy.ctor]、11.4.6/9 [class.copy.assign]、11.4.7/8 [class.dtor]) 一般表示:

  • the function is not user-provided.
  • there's nothing virtual to the class
  • each non-static data member has the relevant trivial function

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

... A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. ...

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

GCC 和 Clang 报告说 S is trivially copyable in C++11 through C++23 standard modes. MSVC reports that S is not trivially copyable 在 C++14 到 C++20 标准模式中。

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

A trivially copyable class is a class that:

  • has no non-trivial copy constructors,
  • has no non-trivial move constructors,
  • has no non-trivial copy assignment operators,
  • has no non-trivial move assignment operators, and
  • has a trivial destructor.

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

N4659 (~ C++17) 说:

A trivially copyable class is a class:

  • where each copy constructor, move constructor, copy assignment operator, and move assignment operator is either deleted or trivial,
  • that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and
  • that has a trivial, non-deleted destructor

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

N4860 (~ C++20) 说:

A trivially copyable class is a class:

  • that has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator,
  • where each eligible copy constructor, move constructor, copy assignment operator, and move assignment operator is trivial, and
  • that has a trivial, non-deleted destructor.

根据这个定义,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 and LLVM bug #39050