Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Default constructor expression and lvalues

My C++ colleagues and I ran into a curious construct:

struct A { int i; };
void foo(A const& a);

int main() {
  foo(A() = A{2}); // Legal
}

The A() = A{2} expression completely befuddled us as it appears to be assigning A{2} to a temporary, default-constructed object. But see it in compiler explorer (https://gcc.godbolt.org/z/2LsfSk). It appears to be a legal statement (supported by GCC 9 and Clang 9), as are the following statements:

struct A { int i; };

int main() {
  A() = A{2};
  auto a = A() = A{3};
}

So it appears, then, that in some contexts A() is an lvalue. Or is something else going on here? Would appreciate some explanation and, preferably, a reference to the C++17 standard.


Update: @Brian found that this is a duplicate of assigning to rvalue: why does this compile?. But would really appreciate if someone could find the appropriate reference in the C++ standard.

like image 921
KyleKnoepfel Avatar asked Dec 20 '19 19:12

KyleKnoepfel


People also ask

What are the default values of the variables in the constructor?

In that case, the default values of the variables are 0. A program that demonstrates default constructors is given as follows. In the above program, the class DemoDC contains a default constructor that initialises num1 and num2 as 10 and 20.

What is a default constructor without arguments?

A constructor without any arguments or with the default value for every argument is said to be the Default constructor . What is the significance of the default constructor? They are used to create objects, which do not have any having specific initial value. Is a default constructor automatically provided?

What is the default constructor of T in C++?

T has a non- const-default-constructible const member without a default member initializer (since C++11) . T has a member (without a default member initializer) (since C++11) which has a deleted default constructor, or its default constructor is ambiguous or inaccessible from this constructor.

What is a final constructor in C++?

final (C++11) A default constructor is a constructor which can be called with no arguments (either defined with an empty parameter list, or with default arguments provided for every parameter). A type with a public default constructor is DefaultConstructible . Contents.


1 Answers

A{} is always an rvalue per [expr.type.conv]

1 A simple-type-specifier or typename-specifier followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer. If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction for the remainder of this subclause.
2 If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression. Otherwise, if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer. If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.

emphasis mine

The reason these works is here is nothing in the standard to stop it from working.

For built in types like int there is [expr.ass]/1

The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue referring to the left operand.

So this stops you from doing int{} = 42;. This section doesn't apply to classes, though. If we look in [class.copy.assign] there is nothing that says that an lvalue is required, but the first paragraph does state

A user-declared copy assignment operator X​::​operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X&, or const volatile X&

Which means

A{} = A{2};

is actually

A{}.operator=(A{2})

Which is legal to do on an rvalue class object since the default operator = for your class has no ref-qualifier to stop it from being called on rvalues. If you add

A& operator=(const A& a) & { i = a.i; }

to A instead of using the default assignment operator then

A{} = A{2};

would no longer compile since the operator= will only work on lvalues now.

like image 93
NathanOliver Avatar answered Oct 29 '22 09:10

NathanOliver