【问题标题】:Declaring defaulted assignment operator as constexpr: which compiler is right?将默认赋值运算符声明为 constexpr:哪个编译器是正确的?
【发布时间】:2019-08-06 12:17:22
【问题描述】:

考虑

struct A1 {
    constexpr A1& operator=(const A1&) = default;
    ~A1() {}
};
struct A2 {
    constexpr A2& operator=(const A2&) = default;
    ~A2() = default;
};
struct A3 {
    ~A3() = default;
    constexpr A3& operator=(const A3&) = default;
};

GCC 和 MSVC 接受所有三个结构。 Clang 拒绝 A1A2(但接受 A3),并显示以下错误消息:

<source>:2:5: error: defaulted definition of copy assignment operator is not constexpr
    constexpr A1& operator=(const A1&) = default;
    ^
<source>:6:5: error: defaulted definition of copy assignment operator is not constexpr
    constexpr A2& operator=(const A2&) = default;
    ^
2 errors generated.

(live demo)

哪个编译器是正确的,为什么?

【问题讨论】:

  • 好问题。以下只是一个猜测......我们知道函数中的constexpr并不意味着const。这意味着该函数是否可以在编译时计算。静默创建的复制赋值运算符前面没有constexpr。这意味着您拥有的 constexpr 是对静默创建的重载。然而,重载不能被默认,这解释了错误。查看以下 3 个代码示例:1) (clang) rextester.com/WLGFD87794, 2) (gcc) rextester.com/RMWQ86797, 3) (vc++) rextester.com/MXIHQ50551

标签: c++ language-lawyer c++17 constexpr


【解决方案1】:

我认为这三个编译器都错了。

[dcl.fct.def.default]/3 说:

只有在隐式声明为 constexpr 的情况下,才可以将未定义为已删除的显式默认函数声明为 constexprconsteval。如果一个函数在它的第一个声明中被显式默认,那么如果隐式声明是,它被隐式地认为是constexpr

复制赋值运算符何时隐式声明constexpr[class.copy.assign]/10:

隐式定义的复制/移动赋值运算符是 constexpr if

  • X 是文字类型,并且
  • [...]

文字类型在哪里,来自[basic.types]/10

如果一个类型是一个字面量类型:

  • [...]
  • 具有以下所有属性的可能具有 cv 限定的类类型:

    • 它有一个微不足道的析构函数,
    • [...]

A1 没有简单的析构函数,因此它的隐式复制赋值运算符不是constexpr。因此,复制赋值运算符格式不正确(gcc 和 msvc 错误接受)。

另外两个没问题,拒绝A2是个clang bug。


注意我引用的 [dcl.fct.def.default] 的最后一点。如果您明确默认,您实际上不必添加constexpr。在可能的情况下,这将是隐含的constexpr

【讨论】:

  • 添加constexpr 应该如果不是constexpr,则会给您一个错误,这可能非常有用。
【解决方案2】:

C++17 标准规定:

15.8.2 复制/移动赋值运算符[class.copy.assign]
...

10 默认且未定义为已删除的类 X 的复制/移动赋值运算符在 odr 使用 (6.2) 时隐含定义(例如,当重载决议选择它以分配给其类类型的对象)或当它在其第一次声明后显式默认时。 隐式定义的复制/移动赋值运算符是constexpr if
(10.1) — X 是文字类型,并且
(10.2) — 选择用于复制/移动每个直接基类子对象的赋值运算符是 constexpr 函数,并且
(10.3) — 对于 X 的每个类类型(或其数组)的非静态数据成员,选择用于复制/移动该成员的赋值运算符是 constexpr 函数。

复制赋值运算符在两种情况下满足上述要求。在第一种情况下,我们有一个非文字类型,因为有非平凡的析构函数。

所以我认为 Clang 在第二种情况下拒绝代码是错误的。

有一个使用 Clang 提交的错误,标题为:Defaulted destructor prevents using constexpr on defaulted copy/move-operator,它显示的症状与 OP 中的代码相同。

来自错误报告状态的 cmets:

当默认析构函数被注释掉(即没有用户声明)时,错误就不再存在了。

如果在复制赋值运算符之前声明析构函数,问题也会消失。

问题中的代码也是如此。

正如@YSC 指出的那样,这里的另一个相关引用是:[dcl.fct.def.default]/3,其中指出:

只有在隐式声明为 constexpr 的情况下,才可以将未定义为已删除的显式默认函数声明为 constexprconsteval。如果一个函数在它的第一个声明中被显式默认,那么如果隐式声明是,那么它被隐式地认为是constexpr

【讨论】:

猜你喜欢
  • 2014-12-31
  • 1970-01-01
  • 2013-08-09
  • 2016-09-15
  • 1970-01-01
  • 1970-01-01
  • 2012-11-26
  • 1970-01-01
  • 2016-12-15
相关资源
最近更新 更多