Default constructor expression and lvalues - c++

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.

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.

Related

C++ interpret the curly bracket in assignment?

I have just tried to use this code and somehow found it not marked error by my compiler.
struct structA{
int x;
int y;
};
...
struct structA var;
var={1,2};
This somehow worked well.
In my limited knowledge, the {,} clause are only allowed as aggregate initialization. How should I interpret it here?
In this case structA is an aggregate, so this form of initialization is allowed.
An aggregate is an array or a class with
no user-provided, explicit, or inherited constructors ([class.ctor]),
no private or protected non-static data members (Clause [class.access]),
no virtual functions, and no virtual, private, or protected base classes ([class.mi]).
So this will be allowed:
struct structA var = {1,2};
But what you actually have is assignment which is also allowed (from C++11) because you are using an initializer list as rhs.
If the right operand is a braced-init-list
if the expression E1 has class type, the syntax E1 = {args...} generates a call to the assignment operator with the braced-init-list
as the argument, which then selects the appropriate assignment
operator following the rules of overload resolution.
Such initialization is allowed for aggregate only, that includes POD
class.
structA is POD (Plain Old Data). Therefore it won't produce error.
If you change your structA to :
struct structA{
int x;
int y;
structA(int, int){} //user-defined constructor
};
It will produce error. See this What is assignment via curly braces called? and can it be controlled? for more information
See the section "Builtin Direct Assignment" on this page:
https://en.cppreference.com/w/cpp/language/operator_assignment
Builtin direct assignment
The direct assignment expressions have the form
lhs = rhs (1)
lhs = {} (2) (since C++11)
lhs = { rhs } (3) (since C++11)
For the built-in operator, lhs may have any non-const scalar type and rhs must be implicitly convertible to the type of lhs.
The direct assignment operator expects a modifiable lvalue as its left operand and an rvalue expression or a braced-init-list (since C++11) as its right operand, and returns an lvalue identifying the left operand after modification.
For non-class types, the right operand is first implicitly converted to the cv-unqualified type of the left operand, and then its value is copied into the object identified by left operand.
When the left operand has reference type, the assignment operator modifies the referred-to object.
If the left and the right operands identify overlapping objects, the behavior is undefined (unless the overlap is exact and the type is the same)
If the right operand is a braced-init-list
if the expression E1 has scalar type,
the expression E1 = {} is equivalent to E1 = T{}, where T is the type of E1.
the expression E1 = {E2} is equivalent to E1 = T{E2}, where T is the type of E1.
if the expression E1 has class type, the syntax E1 = {args...} generates a call to the assignment operator with the braced-init-list as the argument, which then selects the appropriate assignment operator following the rules of overload resolution. Note that, if a non-template assignment operator from some non-class type is available, it is preferred over the copy/move assignment in E1 = {} because {} to non-class is an identity conversion, which outranks the user-defined conversion from {} to a class type.

Copy/assignment of fundamental types

What does the standard say about copy/assignment of fundamental types?
For class types, we have copy constructor, assignment operator, which takes the right hand side as a reference (it must be a reference, otherwise we had infinite recursion):
struct Foo {
Foo(const Foo &);
};
How does this defined for fundamental types?
Look at this example:
const Foo foo;
Foo f = foo;
const int a = 2;
int b = a;
Here, f = foo; odr-uses foo, as copy-constructor takes a reference, right?. If copy of fundamental types had a reference parameter, then b = a would odr-use a as well. Is it the case? If not, how is it handled?
We can trace it. Starting at [dcl.init].
(17.8) - Otherwise, the initial value of the object being initialized
is the (possibly converted) value of the initializer expression.
Standard conversions will be used, if necessary, to convert the
initializer expression to the cv-unqualified version of the
destination type; no user-defined conversions are considered. If the
conversion cannot be done, the initialization is ill-formed. When
initializing a bit-field with a value that it cannot represent, the
resulting value of the bit-field is implementation-defined.
The standard conversion in this case would be the lvalue-to-rvalue conversion on a. But that doesn't odr-use a. For we see in [basic.def.odr]
2 A variable x whose name appears as a potentially-evaluated expression
ex is odr-used by ex unless applying the lvalue-to-rvalue conversion
to x yields a constant expression that does not invoke any non-trivial
functions and, if x is an object, ex is an element of the set of
potential results of an expression e, where either the
lvalue-to-rvalue conversion is applied to e, or e is a discarded-value
expression.
a is a constant expression and substitution of a for x and ex above demonstrates it holds the other half of the condition, so it's not odr-used.

Overload resolution when an argument is an initializer list and the parameter is a reference

struct A { A(int);};
struct B { explicit B(A); B(const B&);};
B b({0});
I have asked a question Overload resolution gets different result between gcc and clang and #Johannes Schaub - litb explained the rules that are active. But I still have some questions about 13.3.3.1.4 Reference binding.
N4527 13.3.3.1.5 [over.ics.list] p1 and p8
1 When an argument is an initializer list (8.5.4), it is not an expression and special rules apply for converting
it to a parameter type.
8 Otherwise, if the parameter is a reference, see 13.3.3.1.4.
13.3.3.1.4 [over.ics.ref] p1 and p2
1 When a parameter of reference type binds directly (8.5.3) to an argument expression, the implicit conversion
sequence is the identity conversion, unless the argument expression has a type that is a derived class of the
parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion (13.3.3.1). [ Example... ]
If the parameter binds directly to the result of applying a conversion function to the
argument expression, the implicit conversion sequence is a user-defined conversion sequence (13.3.3.1.2),
with the second standard conversion sequence either an identity conversion or, if the conversion function
returns an entity of a type that is a derived class of the parameter type, a derived-to-base Conversion.
2 When a parameter of reference type is not bound directly to an argument expression, the conversion sequence
is the one required to convert the argument expression to the underlying type of the reference according
to 13.3.3.1. Conceptually, this conversion sequence corresponds to copy-initializing a temporary of the
underlying type with the argument expression. Any difference in top-level cv-qualification is subsumed by
the initialization itself and does not constitute a conversion.
Question 1: Does "argument expression" include "initializer list"? See 13.3.3.1.5 [over.ics.list] p1 bold phrase above and
1.3.2 [defns.argument]
argument
<function call expression> expression in the comma-separated list bounded by the parentheses (5.2.2)
8.5 [dcl.init] p17
17 The semantics of initializers are as follows. The destination type is the type of the object or reference being
initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly
parenthesized) expression, the source type is not defined.
(17.1) — If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).
(17.2) — If the destination type is a reference type, see 8.5.3.
8.5.3 [dcl.init.ref] p5
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
[...]
(5.2.2.2) — Otherwise, a temporary of type “cv1 T1” is created and copy-initialized (8.5) from the initializer
expression. The reference is then bound to the temporary.
[...]
In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the
reference is said to bind directly to the initializer expression.
Question 2: Does the "bind directly" include the case in which the initializer is an initializer list? In other words, can we use "bind directly" when the initializer is an initializer list?
NOTE: "bind directly" is definition in 8.5.3 which is quoted by 8.5 p17.1 and "initializer is a braced-init-list" is definition in 8.5.4 which is quoted by 8.5 p17.2
//case 5.2.1.2
struct X{};
struct Y{Y(X);};
const Y& y1 = X(); // bind directly
const Y& y2 = {X()}; // bind directly or not?
struct Z{operator X();};
const X& x1 = Z(); // bind directly
const X& x2 = {Z()}; // bind directly or not?
//case 5.2.2.1
struct A{operator int();};
const int& a1 = A(); // bind directly
const int& a2 = {A()}; // bind directly or not?
struct B{B(int);};
const B& b1 = 1; // bind directly
const B& b2 = {1}; // bind directly or not?
//csse 5.2.2.2
int i3 = 2;
double&& rrd3 = i3; // not bind directly
struct A { A(int);};
struct B { explicit B(A); B(const B&);};
B b({0}); // when overload resolution choose B(const B&) as a candidate,
// {0} -> constB& bind directly or not?
Question 3(the main question):
when an argument is an initializer list and the parameter is a reference, 13.3.3.1.5 [over.ics.list] p8 quotes to 13.3.3.1.4 [over.ics.ref], but I can't see any words about argument which is an initializer list. I think the definition of "bind directly" and "argument" is not related with "initializer list".
Can you explain how overload resolution work when an argument is an initializer list and the parameter is a reference?
NOTE: These three questions are related. When you answer the third question, you will answer the first and second.
struct A { A(int);};
struct B { explicit B(A); B(const B&);};
B b1(0); //when overload resolution choose B(const B&) as a candidate,
//0 -> const B& binds directly
//13.3.3.1.4 [over.ics.ref] p1 "If the parameter binds directly..."
A a;
B b2(a) //when overload resolution choose B(const B&) as a candidate,
//a -> const B& binds directly
//13.3.3.1.4 [over.ics.ref] p1 "If the parameter binds directly..."
B b3({0})//when overload resolution choose B(const B&) as a candidate,
//{0} -> const B& binds directly or not?
//if it is not bound directly, 13.3.3.1.4 [over.ics.ref] p2
B b3({a})//when overload resolution choose B(const B&) as a candidate,
//{a} -> const B& binds directly or not?
//if it is not bound directly, 13.3.3.1.4 [over.ics.ref] p2
We simply have a defect, which was reported as http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1536 (I just found that, were not aware of that report previously when writing the other answer).
Going with the interpretation that over.ics.ref is completely initializer list agnostic and is talking about the created temporaries' (the one created by decl.init.list) binding to the reference seems problematic to me. In particular, over.ics.list says that over.ics.ref will delegate to over.ics.list for initiapization of the temporary, indicating that over.ics.ref is active already before creation of the temporary (also there are cases in decl.init.list where no temporary is created). Also { } to ClassType& should be a user defined conversion but the temporary rvalue will be bound directly by the refefence when considering the conversion isolated from the initializer list argument.

Reference to const T initialized by value of type other than T

For the following code:
struct A {
explicit A(int) {}
};
const A& a(1); // error on g++/clang++/vc++
const A& b = 1; // error on g++/clang++/vc++
const A& c{1}; // ok on g++/clang++, error on vc++
const A& d = {1}; // error on g++/clang++/vc++
Which one(s) of the 4 initialization is(are) legal?
If we ignore vc++ first, it seems that the difference between direct-init and copy-init is not behaving consistently here. If the third one is well-formed because it's direct-init, why does the first one which is also direct-init fail to compile? What's the logic behind this? Or it's just a bug for g++/clang++ and vc++ handles it correctly?
If you're using braced-init-lists and the destination type of an initialization is a reference:
[dcl.init.list]/3 (from n3690)
Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is
reference-related to E, the object or reference is initialized from
that element; if a narrowing conversion (see below) is required to
convert the element to T, the program is ill-formed.
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized or
direct-list-initialized, depending on the kind of initialization for
the reference, and the reference is bound to that temporary. [Note: As
usual, the binding will fail and the program is ill-formed if the
reference type is an lvalue reference to a non-const type. — end note]
Otherwise, if the initializer list has no elements, the object is value-initialized.
For the two examples const A& c{1}; and const A& d = {1};, the second bullet of the quotation above applies. The first one direct-list-initializes a const A, the second one copy-list-initializes a const A. Copy-initialization selecting an explicit constructor is ill-formed, see [over.match.list]/1.
If you're not using braced-init-lists, there's no difference between copy-initialization and direct-initialization as far as I can tell. The last bullet of [dcl.init.ref]/5 applies in all cases:
Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression
using the rules for a non-reference copy-initialization (8.5). The reference is then bound to the
temporary.
Copy-initialization cannot select an explicit ctor, see [over.match.copy]/1 (it's not viable).
Conclusion:
const A& c{1};
is legal. The other are not, because they either use copy-initialization or copy-list-initialization and the only viable / selected ctor is explicit.
Your struct can only be created by an explicit call to it's constructor. No implicit conversion is allowed.
A const reference must point to an existing object of that type on construction.
None of your lines create an object of type A by calling it's explicit constructor. So I don't see why any of those lines should properly initialize a const reference.

Ambiguous assignment operator

I have two classes, one of which, say, represents a string, and the other can be converted to a string:
class A {
public:
A() {}
A(const A&) {}
A(const char*) {}
A& operator=(const A&) { return *this; }
A& operator=(const char*) { return *this; }
char* c;
};
class B {
public:
operator const A&() const {
return a;
}
operator const char*() const {
return a.c;
}
A a;
};
Now, if I do
B x;
A y = x;
It triggers copy constructor, which compiles fine. But if I do
A y;
y = x;
It complains about ambiguous assignment, and can't choose between =(A&) and =(char*). Why the difference?
There is a difference between initialization and assignment.
In initialization, that is:
A y = x;
The actual call depends on the type of x. If it is the same type of y, then it will be like:
A y(x);
If not, as in your example, it will be like:
A y(static_cast<const A&>(x));
And that compiles fine, because there is no ambiguity any more.
In the assignment there is no such special case, so no automatic resolution of the ambiguity.
It is worth noting that:
A y(x);
is also ambiguous in your code.
There is §13.3.1.4/(1.2), only appertaining to (copy-)initialization of objects of class type, that specifies how candidate conversion functions for your first case are found:
Under the conditions specified in 8.5, as part of a
copy-initialization of an object of class type, a user-defined
conversion can be invoked to convert an initializer expression to the
type of the object being initialized. Overload resolution is used to
select the user-defined conversion to be invoked. […] Assuming that
“cv1 T” is the type of the object being initialized, with T a class
type, the candidate functions are selected as follows:
The converting constructors (12.3.1) of T are candidate
functions.
When the type of the initializer expression is a class type
“cv S”, the non-explicit conversion functions of S and its base
classes are considered. When initializing a temporary to be bound to
the first parameter of a constructor where the parameter is of type
“reference to possibly cv-qualified T” and the constructor is called
with a single argument in the context of direct-initialization of an
object of type “cv2 T”, explicit conversion functions are also
considered. Those that are not hidden within S and yield a type
whose cv-unqualified version is the same type as T or is a derived
class thereof are candidate functions. […] Conversion functions that return “reference to X” return lvalues or xvalues,
depending on the type of reference, of type X and are therefore considered to yield X for this process of selecting candidate functions.
I.e. operator const char* is, though being considered, not included in the candidate set, since const char* is clearly not similar to A in any respect. However, in your second snippet, operator= is called as an ordinary member function, which is why this restriction doesn't apply anymore; Once both conversion functions are in the candidate set, overload resolution will clearly result in an ambiguity.
Note that for direct-initialization, the above rule doesn't apply either.
B x;
A y(x);
Is ill-formed.
A more general form of this result is that there can never be two user-defined conversions in one conversion sequence during overload resolution. Consider §13.3.3.1/4:
However, if the target is
the first parameter of a constructor or […]
and the constructor […] is a candidate
by
13.3.1.3, when the argument is the temporary in the second step of a class copy-initialization, or
13.3.1.4, 13.3.1.5, or 13.3.1.6 (in all cases),
user-defined conversion sequences are not considered. [Note: These
rules prevent more than one user-defined conversion from being applied
during overload resolution, thereby avoiding infinite recursion. — end
note ]