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).
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.
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;
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.
In the below code I call step as a member function and as a global function on a temporary value. The member function is allowed, and works, whereas the global function is disallowed due to invalid initialisation of non-const reference of type ‘kludge&’ from an rvalue of type ‘kludge’.
I'm trying to understand, from a language perspective, why one behaviour is allowed and the other is not. Technically both calls and functions seem like they'd be compiled identically, or at least could be.
#include <iostream>
struct kludge {
int a;
kludge() {
a = 1;
}
kludge & step() {
a++;
std::cout << a << ",";
return *this;
}
};
kludge get() {
kludge t;
return t;
}
kludge & step( kludge & t ) {
t.a++;
std::cout << t.a << ",";
return t;
}
int main() {
get().step();
step( get() );
}
You cannot bind rvalues to non-const lvalue references1. That applies to step(get()) as the parameter of step, which is a non-const lvalue reference, cannot be bound to the prvalue (pure rvalue) get().
However, member functions can per se be called on object arguments of every value category, be it lvalue or rvalue - [over.match.funcs]/4 and /5:
For non-static member functions, the type of the implicit object
parameter is
“lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
[..]
For non-static member functions declared without a ref-qualifier, an
additional rule applies:
even if the implicit object parameter is
not const-qualified, an rvalue can be bound to the parameter as long
as in all other respects the argument can be converted to the type of
the implicit object parameter. [ Note: The fact that such an
argument is an rvalue does not affect the ranking of implicit
conversion sequences (13.3.3.2). — end note ]
But if you use so-called ref-qualifiers, you can restrict the value categories that are valid for a particular member function. That is, if you write:
kludge & step() & { /* .. */ }
The call get().step() will be ill-formed too.
1)
This is a well-known fact, but here is [dcl.init.ref]/5, heavily shortened:
A reference to type “cv1 T1” is initialized by an expression of
type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue [..]
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,”
[..]
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the
reference shall be an rvalue reference.
Temporary cannot bind to non-const reference
step( get() );
// ~~~~~ Creates a temporary object (r-value)
// But step( ) excepts a non-const reference
I'm trying C++ pass-by-reference using this simple code:
#include <iostream>
int square(int &x)
{
return x*x;
}
int main()
{
std::cout<<"Square of 4 is: "<<square(4)<<std::endl;
return 0;
}
But, when I try to run it, I get the following:
UPDATE
I get the following error after modifying the code based on #Pablo Santa Cruz's answer (I just screen captured part of the error):
Why is that?
Thanks.
You cannot pass temporaries to non-constant references.
Make square accept a const int &:
int square(const int &x)
{
return x*x;
}
You can't pass a constant (4) if you are expecting a reference.
You must pass a variable:
int n = 4;
square(n);
Having said that, you probably want something like this:
void square(int &x)
{
x = x*x;
}
Not a function returning an int.
It's because of § 8.5.3 5 of C++03:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
If the initializer expression
is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
has a class type (i.e., T2 is a class type) and can be implicitly converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3” 92) (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6) and choosing the best one through over- load resolution (13.3)),
then the reference is bound directly to the initializer expression lvalue in the first case, and the reference is bound to the lvalue result of the conversion in the second case. In these cases the reference is said to bind directly to the initializer expression. [Note: the usual lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done. ]
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 “cv1 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.
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.
Above, "cv*" refers to the modifiers "const" and "volatile", and "T*" refer to type names. For example, const int (cv = "const", T = "int"), const volatile std::string (cv = "const volatile", T = "std::string"), char* (cv = "", T = "char*").
In short, rvalues are allowed to bind only to const references.
Update
If your square now returns void (code or it didn't happen), then the new error is because there is no operator<<(std::out&, void).
Compiler creates a temporary object for constant 4 which can not be passed to a function as a non-const reference. Create a int object in main and pass it to the function or take the function parameter by const-reference or by copy.
The declaration
int square(int& x);
says that the function square may change its argument (although it doesn't really). So to call square(4) is ridiculous: the program needs to be ready to change the number 4.
As people have noted, you can either change the function to specify that it won't change its argument:
int square(const int& x); // pass reference to const type
// OR
int square(int x); // pass by value
Or you can call your original square using a value that can be modified.
int square(int& x);
// ...
int num = 4;
square(num);
// now (as far as the compiler knows) num might no longer be 4!
A reference must be an l-value-- basically, something you can see on the left side of an assignment statement.
So basically, you need to assign to a variable in main first. Then you can pass it into square.
A reference aliases another variable. "4" is not a variable, and therefore your reference has nothing to alias. The compiler therefore complains.
What you want is this:
#include <iostream>
int square(int x)
{
return(x * x);
}
int main()
{
std::cout << "Square of 4 is: "<< square(4) << std::endl;
return 0;
}
Did you notice how I got rid of the & in int square(int &x)? The & means pass-by-reference. It takes a variable. The 4 in square(4) is a constant.
Using pass-by-reference, you can change the value of x:
void square2(int &x)
{
x = x * x;
return;
}
Now:
int x = 5;
square2(x);
// x == 25
Although, I don't think you want this. (Do you??) The first method is a lot better.