This question pertains to this code snippet reproduced below:
struct A {
A():b(0) { }
A(const A&, int b = 0):b(b) { }
int b;
};
int main() {
A a;
const A& a1 = { a };
const A& a2 = { a, 1 };
a.b = 2;
std::cout << a1.b << a2.b << std::endl;
}
The right hand side of the assignment of a1 can be a single value in constructs or an argument list for construction. Is there anywhere in the standard that specifies which interpretation takes precedence? For a2, it is a construction of a temporary A whose address is assigned to a2, if I did not misunderstand.
BTW, compiling this code by clang++ in Coliru produced output 21. gcc++-4.8 output 01.
The definition of list-initialization has changed quite a bit since the publication of the C++11 Standard due to defect reports.
From draft n3485 (after the Standard has been published, with some corrections but without C++1y features) [dcl.init.list]/3
List-initialization of an object or reference of type T is defined as follows:
If T is an aggregate [...]
Otherwise, if the initializer list has no elements [...]
Otherwise, if T is a specialization of std::initializer_list<E> [...]
Otherwise, if T is a class type [...]
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 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 list-initialized, and the reference is bound to that temporary
[...]
In the latest draft from the Committee's github repo (ce016c64dc), only a slight change applies here to one point [dcl.init.list]/3:
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.
From draft n3242 (before the Standard) [dcl.init.list]/3:
List-initialization of an object or reference of type T is defined as follows:
If the initializer list has no elements [...]
Otherwise, if T is an aggregate [...]
Otherwise, if T is a specialization of std::initializer_list<E> [...]
Otherwise, if T is a class type [...]
Otherwise, if T is a reference to class type or if T is any reference type and the initializer list has no elements, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to that temporary.
[...]
(I don't have a copy of the Standard itself right now.)
Let's assume your compiler implements the proposed resolutions to the defect reports. Then, the first example
const A& a1 = { a };
initializes like const A& a1 = a; (no temporary); and the second example
const A& a2 = { a, 1 };
initializes like const A& a2 = A(a,1);.
8.5.4/3 List-initialization of an object or reference of type T is defined as follows:
...
Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is list-initialized, and the reference is bound to
that temporary.
In your example, a1 does not bind directly to a, but to a temporary copy-constructed from a.
Related
Is this code UB?
struct A
{
void nonconst() {}
};
const A& a = A{};
const_cast<A&>(a).nonconst();
In other words, is the (temporary) object originally const? I've looked through the standard but cannot find an answer so would appreciate quotations to relevant sections.
Edit: for those saying A{} is not const, then can you do A{}.nonconst() ?
The initialization of the reference a is given by [dcl.init.ref]/5 (bold mine):
Otherwise, if the initializer expression
is an rvalue (but not a bit-field)[...]
then the value of the initializer expression in the first case and the result of the conversion in the second case is called the converted initializer.
If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied.
So it means that the type prvalue expression that initialize the reference, A{}, is adjusted to const A.
Then [conv.rval] states:
A prvalue of type T can be converted to an xvalue of type T.
This conversion initializes a temporary object ([class.temporary]) of type T.
So the type of the temporary object, bound to the reference is the same as the adjusted prvalue type: const A.
So the code const_cast<A&>(a).nonconst(); is undefined behavior.
The type of a temporary is whatever type you declared it with.
Unfortunately, as Oliv points out in their answer reference initialization rules transform the type to match the reference type so in this case a actually refers to a const A. It is basically doing
using const_A = const A;
const A& a = const_A{};
Because you can actually create constant prvalues if you ever want to stop a overload set from accepting a constant prvalue you need to have
ret_type function_name(some_type const&&) = delete;
otherwise if you have
ret_type function_name(some_type const&)
in the overload set it then the constant prvalue would bind to that if you only deleted
ret_type function_name(some_type&&)
instead. You can see this working with
struct bar{};
void foo(bar const&) { std::cout << "void foo(bar const&)\n"; }
void foo(bar&&) =delete;
using c_bar = const bar;
int main()
{
foo(c_bar{});
}
Here, void foo(bar const&) gets called since c_bar{} is actually const instead of getting a deleted function error if you had used foo(bar{});. Adding
void foo(bar const&&) = delete;
is needed to actually stop foo(c_bar{}); from compiling.
Is this code UB?
struct A
{
void nonconst() {}
};
const A& a = A{};
const_cast<A&>(a).nonconst();
In other words, is the (temporary) object originally const? I've looked through the standard but cannot find an answer so would appreciate quotations to relevant sections.
Edit: for those saying A{} is not const, then can you do A{}.nonconst() ?
The initialization of the reference a is given by [dcl.init.ref]/5 (bold mine):
Otherwise, if the initializer expression
is an rvalue (but not a bit-field)[...]
then the value of the initializer expression in the first case and the result of the conversion in the second case is called the converted initializer.
If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied.
So it means that the type prvalue expression that initialize the reference, A{}, is adjusted to const A.
Then [conv.rval] states:
A prvalue of type T can be converted to an xvalue of type T.
This conversion initializes a temporary object ([class.temporary]) of type T.
So the type of the temporary object, bound to the reference is the same as the adjusted prvalue type: const A.
So the code const_cast<A&>(a).nonconst(); is undefined behavior.
The type of a temporary is whatever type you declared it with.
Unfortunately, as Oliv points out in their answer reference initialization rules transform the type to match the reference type so in this case a actually refers to a const A. It is basically doing
using const_A = const A;
const A& a = const_A{};
Because you can actually create constant prvalues if you ever want to stop a overload set from accepting a constant prvalue you need to have
ret_type function_name(some_type const&&) = delete;
otherwise if you have
ret_type function_name(some_type const&)
in the overload set it then the constant prvalue would bind to that if you only deleted
ret_type function_name(some_type&&)
instead. You can see this working with
struct bar{};
void foo(bar const&) { std::cout << "void foo(bar const&)\n"; }
void foo(bar&&) =delete;
using c_bar = const bar;
int main()
{
foo(c_bar{});
}
Here, void foo(bar const&) gets called since c_bar{} is actually const instead of getting a deleted function error if you had used foo(bar{});. Adding
void foo(bar const&&) = delete;
is needed to actually stop foo(c_bar{}); from compiling.
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.
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.
Based on
http://www.cplusplus.com/reference/stl/vector/vector/
explicit vector ( const Allocator& = Allocator() );
This vector constructor takes a reference parameter which has default value of Allocator(). What I learn from this function signature is that a function can take a reference parameter with default value.
This the demo code I play with VS2010.
#include "stdafx.h"
#include <iostream>
using namespace std;
void funA(const int& iValue=5) // reference to a template const int 5 why?
{
cout << iValue << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
funA();
funA(10);
return 0;
}
are there some rules to guide this syntax usage (i.e. a reference parameter with a default value)?
Const references may be bound to temporary objects, in which case the lifetime of the temporary extends to the lifetime of the reference.
The only rules I can think of are (a) that the reference must be const, because you can't bind a non-const reference to a temporary, and (b) that it's generally better not to use const references to pass built-in types. In other words:
(a)
void f(T& t = T(23)) {} // bad
void g(const T& t = T(23)) {} // fine
(b)
void f(const int& i = 23) {} // sort of ok
void g(int i = 23) {} // better
This behavior is defined in § 8.3.6 5 of c++03:
A default argument expression is implicitly converted (clause 4) to the parameter type. The default argument expression has the same semantic constraints as the initializer expression in a declaration of a variable of the parameter type, using the copy-initialization semantics (8.5).
That is, const Type& var = val is a valid parameter declaration only if it's also a valid variable declaration. According to § 8.5.3 5, it is. For const Allocator& = Allocator(), the following applies:
Otherwise, the reference shall be to a non-volatile const type (i.e., cv1 shall be const). [...]
If the initializer expression is an rvalue, with T2 a class type, and "cv1 T1" is reference-compatible with "cv2 T2," the reference is bound in one of the following ways (the choice is implementation defined):
The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.
A temporary of type "cv2 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.
The constructor that would be used to make the copy shall be callable whether or not the copy is actually done. [...]
Otherwise, [...]
For const int& iValue=5, the next case applies:
Otherwise, the reference shall be to a non-volatile const type (i.e., cv1 shall be const). [...]
If the initializer expression is an rvalue[...]
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. If T1 is reference-related to T2, cv1 must be the same cv-qualification as, or greater cv-qualification than, cv2; otherwise, the program is ill-formed. [Example:
const double& rcd2 = 2; // rcd2 refers to temporary with value 2.0
const volatile int cvi = 1;
const int& r = cvi; // error: type qualifiers dropped
---end example]
In short, a real, though perhaps temporary, variable is created so the reference can refer to it. It's allowed in parameter declarations exactly so that reference parameters can take default values. Otherwise, it would be a needless restriction. The more orthogonal a language is, the easier it is to keep in your head, as you don't need to remember as many exceptions to the rules (though, arguably, allowing const references but not non-const references to be bound to rvalues is less orthogonal than disallowing any reference to be bound to an rvalue).