Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Declaring defaulted assignment operator as constexpr: which compiler is right?

Consider

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 and MSVC accept all three structs. Clang rejects A1 and A2 (but accepts A3), with the following error message:

<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)

Which compiler is correct, and why?

like image 338
cpplearner Avatar asked Mar 15 '19 12:03

cpplearner


People also ask

Which one is the right declaration of the copy assignment operator?

Because the copy assignment operator is always declared for any class, the base class assignment operator is always hidden.

How do you write an assignment operator?

The assignment operator is used to assign the value, variable and function to another variable. Let's discuss the various types of the assignment operators such as =, +=, -=, /=, *= and %=. Example of the Assignment Operators: A = 5; // use Assignment symbol to assign 5 to the operand A.

How do you define assignment operator?

An assignment operator is the operator used to assign a new value to a variable, property, event or indexer element in C# programming language. Assignment operators can also be used for logical operations such as bitwise logical operations or operations on integral operands and Boolean operands.

How do you overload an assignment operator?

Assignment Operators Overloading in C++You can overload the assignment operator (=) just as you can other operators and it can be used to create an object just like the copy constructor. Following example explains how an assignment operator can be overloaded.


2 Answers

I think all three compilers are wrong.

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

An explicitly-defaulted function that is not defined as deleted may be declared constexpr or consteval only if it would have been implicitly declared as constexpr. If a function is explicitly defaulted on its first declaration, it is implicitly considered to be constexpr if the implicit declaration would be.

When is the copy assignment operator implicitly declared constexpr? [class.copy.assign]/10:

The implicitly-defined copy/move assignment operator is constexpr if

  • X is a literal type, and
  • [...]

Where a literal type is, from [basic.types]/10:

A type is a literal type if it is:

  • [...]
  • a possibly cv-qualified class type that has all of the following properties:

    • it has a trivial destructor,
    • [...]

A1 doesn't have a trivial destructor, so its implicit copy assignment operator isn't constexpr. Hence that copy assignment operator is ill-formed (gcc and msvc bug to accept).

The other two are fine, and it's a clang bug to reject A2.


Note the last bit of [dcl.fct.def.default] that I quoted. You don't actually have to add constexpr if you're explicitly defaulting. It would be implicitly constexpr where that is possible.

like image 188
Barry Avatar answered Oct 22 '22 03:10

Barry


The C++17 standard states:

15.8.2 Copy/move assignment operator [class.copy.assign]
...

10 A copy/move assignment operator for a class X that is defaulted and not defined as deleted is implicitly defined when it is odr-used (6.2) (e.g., when it is selected by overload resolution to assign to an object of its class type) or when it is explicitly defaulted after its first declaration. The implicitly-defined copy/move assignment operator is constexpr if
(10.1) — X is a literal type, and
(10.2) — the assignment operator selected to copy/move each direct base class subobject is a constexpr function, and
(10.3) — for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is a constexpr function.

The copy-assignment operator satisfies the above requirements in two of the cases. In the first case, we have a non-literal type because of the non-trivial destructor.

So I believe Clang is wrong to reject the code in the second case.

There is a bug filed with Clang titled: Defaulted destructor prevents using constexpr on defaulted copy/move-operator which shows the same symptoms as the code in the OP.

The comments from the bug report state:

When defaulted destructor is commented out (i.e. not user declared), then errors cease to exist.

and

The problem also goes away if you declare the destructor before the copy assignment operator.

This is true of the code in the question as well.

As @YSC points out, another relevant quote here is:[dcl.fct.def.default]/3 which states:

An explicitly-defaulted function that is not defined as deleted may be declared constexpr or consteval only if it would have been implicitly declared as constexpr. If a function is explicitly defaulted on its first declaration, it is implicitly considered to be constexpr if the implicit declaration would be.

like image 28
P.W Avatar answered Oct 22 '22 02:10

P.W