Class reference to an object instantiated by a literal - c++

What is happening here?
struct A {
A (int){std::cout<<'A';}
};
const A& a = 3;
My assumption is since the literal 3 is an rvalue, temporary materialization conversion is applied. A temporary object is created by calling the constructor thus printing A and the reference is bound to the resulting object.

You are correct. a is an lvalue reference [dcl.ref]/2 to a const-qualified class type, the initializer expression 3 is a prvalue [expr.prim.literal]/1 of non-class type int [lex.icon]/2 that is neither reference-related nor reference-compatible to const A [dcl.init.ref]/4. Therefore, [dcl.init.ref]/5.4.1 would seem to apply. There is a converting constructor [class.conv.ctor]/1, which will be used to convert 3 to a prvalue of type const A, which is then used to initialize the reference. This time around, the intializer expression now is a prvalue of type const A, which is reference related to const A. Thus, [dcl.init.ref]/5.3 should apply, which will perform temporary materialization [conv.rval]. The lifetime of the temporary object created in the process will be extended [class.temporary]/6 since it is being bound to the reference a which lives in global namespace scope…

implicit conversion was occured. it converted from 3 to an object of A, then reference a point to this object.
better practice is add keyword explicit in constructor which take only one parameter like this. then no implicit conversion happen. then below code will throw error.
refer:
https://www.geeksforgeeks.org/g-fact-35/
https://www.learncpp.com/cpp-tutorial/9-13-converting-constructors-explicit-and-delete/
struct A {
explicit A (int){std::cout<<'A';}
};
const A& a = 3;

Related

Temporary initialization and Reference initialization

I am trying to understand how reference initialization. For example, let's look at a typical example.
double val = 4.55;
const int &ref = val;
I can think of 2 possibilities of what is happening in the above snippet.
Possibility 1
The usual explanation is given as follows:
Here a temporary(prvalue) of type int with value 4 is created and then the reference ref is bound to this temporary(prvalue) int object instead of binding to the variable val directly. This happens because the type of the variable val on the right hand side is double while on the left hand side we have a reference to int. But for binding a reference to a variable the types should match. Moreover, the lifetime of the temporary prvalue is extended.
Possibility 2
I think there is another possibility that could happen which is as follows:
Here a temporary(prvalue) of type int with value 4 is created. But since const int &ref expects a glvalue and currently we've a prvalue, the temporary materialization kicks in and so the prvalue is converted to an xvalue. Then the reference ref is bound to this materialized xvalue(since xvalue is also a glvalue) instead of binding to the variable val directly. This happens because the type of the variable val on the right hand side is double while on the left hand side we have a reference to int. But for binding a reference to a variable the types should match. Moreover, the lifetime of the materialized temporary xvalue is extended.
My questions are:
Which of the above explanation is correct according to the C++11 standard. I am open to accept that none of the explanation above is correct in which case what is the correct explanation.
Which of the above explanation is correct according to the C++17 standard. I am open to accept that none of the explanation above is correct in which case what is the correct explanation.
I am also confused to whether a prvalue in the first step of both of the possibilities above, is actually a temporary object? Or the xvalue is the actual object. I mean do we have 2 temporary objects, like the first one due to "conversion to prvalue" and second one due to the "prvalue to xvalue" conversion(temporary materiliazation). Or do we only have one temporary which is due to the "prvalue to xvalue" temporary materialization.
PS: I am not looking for a way to solve this. For example, i know that i can simply write:
const double &ref = val;. My aim is to understand what is happening according to C++11 and C++17 standards.
val in const int &ref = val; is a lvalue, not a prvalue. Even if it is converted to a prvalue, this doesn't mean creation of a temporary in either C++11 or C++17. In C++17 this isn't the case since only prvalue-to-xvalue conversion (and some special cases not relevant here) creates a temporary, while in C++11 [conv.lval]/2 says that only for class types lvalue-to-rvalue conversion creates a temporary.
The initialization of a reference is explained in [dcl.init.ref].
In C++11, all previous cases fall through and so according to [dcl.init.ref]/5.2.2 a temporary of the destination type is created and initialized by copy-initialization from the initializer expression and the reference is bound to that temporary. In this copy-initialization the lvalue val is converted to a prvalue of type double and then to a prvalue of type int, neither of these steps creating additional temporaries.
In C++17, all cases fall through until [dcl.init.ref]/5.2.2, which states that the initializer expression is first implicitly converted to a prvalue of the destination type, which does not imply creation of a temporary, and then the temporary materialization conversion (prvalue-to-xvalue conversion) is applied and the reference bound to the result, i.e. the xvalue referring to the temporary.
In the end there is always exactly one temporary, the one created according to the rule in [dcl.init.ref].
Lets look at each of the cases(C++11 vs C++17) separately.
C++11
From decl.init.ref 5.2.2:
Otherwise, a temporary of type “ cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy-initialization ([dcl.init]). The reference is then bound to the temporary.
One more important thing to note is that from basic.lval#4:
Class prvalues can have cv-qualified types; non-class prvalues always have cv-unqualified types...
When applied to your example, this means that a temporary of type int is created and is initialized from the initializer expression val using the rules for a non-reference copy-initialization. The temporary int so created has the value category of prvalue if/when used as an expression.
Next, the reference ref is then bound to the temporary int created which has value 4. Thus,
double val = 4.55;
const int &ref = val; // ref refers to temporary with value 4
C++17
From decl.init.ref 5.2.2.2:
Otherwise, the initializer expression is implicitly converted to a prvalue of type “cv1 T1”. The temporary materialization conversion is applied and the reference is bound to the result.
When applied to your example, this means that the initializer expression val is implicitly converted to a prvalue of type const int. Now we currently have a prvalue const int. But before temporary materialization is applied, expr 6 kicks in which says:
If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.
This means before temporary materialization could happen, the prvalue const int is adjusted to prvalue int.
Finally, temporary materialization is applied and and ref is bound to the resulting xvalue.
Here's my take for C++17:
double val = 4.55;
const int &ref = val;
We're binding a const int reference to the lvalue expression of type double denoted by val. According to the declaration of references;
Otherwise, the initializer expression is implicitly converted to a prvalue of type “T1”.
we convert the expression val to type int. Note that this implicit type! conversion fits with the draft.
Next,
The temporary materialization conversion is applied, considering the type of the prvalue to be “cv1 T1”, ...
in order to apply the temporary materialization (also known as prvalue -> xvalue conversion), we must have fully matched types so the expression is aditionally converted to const int is considered to be const int without any conversions (source). The value category remains the same (prvalue). And finally,
... and the reference is bound to the result.
we have a reference binding of type T to a prvalue of type T which induces temporary materialization (source) and val gets converted to xvalue. The created temporary object is of type const int and value of 4.
EDIT: And to answer your questions, of course. So neither of the given possibilities aren't technically correct for C++17 right at the start. Strictly speaking, neither prvalues nor xvalues are temporary objects. Rather, they are value category of expressions and expression might (or might not) denote a (temporary) object. So technically, xvalues denote temporary objects while prvalues don't. You could still say that xvalues are temporary objects, that's completely fine by me as long as you know what you're talking about.

Are temporaries bound to const references considered const objects? [duplicate]

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 temporary object originally const?

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.

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.

Function takes a reference parameter with a default value

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