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

问题描述 投票:0回答:1

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

当由 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 的行为。这是正确的吗?

c++ language-lawyer c++20 type-traits
1个回答
8
投票

GCC 和 Clang 报告称

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 #96288LLVM bug #38398

© www.soinside.com 2019 - 2024. All rights reserved.