Why auto&& is not rvalue reference?
Widget&& var1 = Widget(); // rvalue reference
auto&& var2 = var1; //var2 not rvalue reference
below are rvalue reference example
void f(Widget&& param); // rvalue reference
Widget&& var1 = Widget(); // rvalue reference
Why var2 is not rvalue reference but f and var2 are rvalue references?
auto&& is a declaration's equivalent of forwarding references (with identical deduction rules). As such, it will be deduced to an lvalue reference when the initializer is an lvalue. However, var is an lvalue (as it is the name of a variable), hence var2 is an lvalue reference.
Once the type of the initializer has been determined, the compiler determines the type that will replace the keyword auto using the rules for template argument deduction from a function call (see template argument deduction#Other contexts for details). The keyword auto may be accompanied by modifiers, such as const or &, which will participate in the type deduction.
For example, given
const auto& i = expr;
The type of i is exactly the type of the argument u in an imaginary
template template<class U>
void f(const U& u)
If the function call f(expr) was compiled.
In general , it can be think as below .
template template<class U>
void f(paramtype u)
Therefore, auto&& may be deduced either as an lvalue reference or rvalue reference according to the initializer.
In your case , imaginary template would look like
template template<class U>
void f(U&& var2){}
f(var1)
Here ,var1 is named rvalue which is being treated as lvalue, so var2 will be deduced as lvalue .
Consider the following examples:
auto&& var2 = widget() ; //var2 is rvalue reference here .
int x=10;
const int cx=10;
auto&& uref1 = x; // x is int and lvalue, so uref1's type is int&
auto&& uref2 = cx; // cx is const int and lvalue, so uref2's type is const int&
auto&& uref3 = 27; // 27 is int and rvalue, so uref3's type is int&&
Related
#include <iostream>
using namespace std;
namespace mine {
template <typename T>
struct remove_rval {
using type = T;
};
template <typename T>
struct remove_rval<T&&> {
using type = T;
};
template <typename T>
void g(const T& = typename remove_rval<T>::type())
cout << __PRETTY_FUNCTION__ << endl;
}
}
int main()
{
mine::g<int&&>(); // doesn't work, because of explicit template?
const int& i2 = mine::remove_rval<int&&>::type(); // works, sanity check
return 0;
}
The function template I wrote fails to compile. From my understanding of c++, you can assign an rvalue to a constant lvalue reference. But, in this situation, it is like the deduced type disregards the 'const' qualifier when assigning the function default value. Why is this?
From dcl.ref/p6:
If a typedef-name ([dcl.typedef], [temp.param]) or a decltype-specifier ([dcl.type.decltype]) denotes a type TR that is a reference to a type T, an attempt to create the type lvalue reference to cv TR creates the type lvalue reference to T, while an attempt to create the type rvalue reference to cv TR creates the type TR.
Thus in your example, when T = int&& :
const T& collapses to T&(which isint&) and not const T&(which is const int&) according to the above quoted statement. And since we can't bind an rvalue like remove_rval<T>::type() to a non-const lvalue reference, you get the mentioned error.
Thus, even though the form of the function parameter in g is a reference to const T aka const lvalue reference(i.e., const T&), the first call to g instantiates g with a reference to non-const T aka non-const lvalue reference(i.e., T& =int&) as the parameter:
template<>
void g<int &&>(int&)
{
//operator<< called here
}
And since the parameter is int&, we cannot bind an rvalue like remove_rval<int&&>::type() to that parameter.
void g(const T& = typename remove_rval<T>::type())
Reference collapsing rules make const T& equal to U& when T = U&&. Lvalue-references can't bind to temporaries like remove_rval<T>::type(). You can instead simply pass int as a template argument and the parameter's type will correctly be const int&.
The following code snippet is excerpted from cppref:
std::tuple<int, int&> f();
auto [x, y] = f();
// decltype(x) is int
// decltype(y) is int&
const auto [z, w] = f();
// decltype(z) is const int
// decltype(w) is int&
My question is at the last line:
Why is decltype(w) int& rather than const int&?
Jarod42 answered the question the question in the comments, let me just cite the relevant part of the standard here, from [dcl.struct.bind]¹:
Given the type Ti designated by std::tuple_element::type, variables are introduced with unique names ri of type “reference to Ti” initialized with the initializer ([dcl.init.ref]), where the reference is an lvalue reference if the initializer is an lvalue and an rvalue reference otherwise.
Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti.
Hence in const auto [z, w] = f();, you have const T1 with T1 being int and const T2 with T2 being int&. As const modifies what's on its left, this becomes int& const and results in int&.
Note that int& const becoming int& is only possible in template argument substitution, i.e., this won't compile:
int n = 42;
int& const doesntWork = n; // Error: 'const' qualifiers cannot be applied to 'int&'
but this does:
template <class T> void f(const T t)
{
++t;
}
int n = 42;
f<int&>(n);
where the identical contraction from int& const to int& as above takes place.
¹ Thanks to #cpplearner for pointing me to the exact paragraph here.
It would be the reference itself rather than the referenced value that would be const. As references aren't modifiable anyway there is no such thing as a constant reference.
I am reading Bjarne's Rvalue Reference Quick Look and came to the following example:
template <class T, class A1>
std::shared_ptr<T>
factory(A1& a1)
{
return std::shared_ptr<T>(new T(a1));
}
This is much better. If a const-qualified type is passed to the
factory, the const will be deduced into the template parameter (A1 for
example) and then properly forwarded to T's constructor.
I do not understand how ::factory() can accept a const reference. Bjarne just states that the const will be deduced into the template parameter. What exactly does this mean?
If you pass in an lvalue of type const X, then A1 will be deduced as const X, and you will get a function that looks like
std::shared_ptr<T> factory(const X& a1) { ... }
template type deduction normally happen in three cases:
Parameter type is a reference or pointer, but not a universal
reference Parameter type is a universal reference Parameter type
is a neither reference nor pointer
your case falls into non-universal reference parameters deduction. The rules will be:
if expression is a reference, ignore that
pattern-match expression's type against parameter type to determine type T
For example:
template<typename T>
void factory(T& param); // param is a reference
int x = 42; // int
const int cx = x; // copy of int
const int& rx = x; // ref to const view of int
factory(x); // 1, T = int, param's type = int&
factory(cx); // 2, T = const int, param's type = const int&
factory(rx); // 3, T = const int, param's type = const int&
Now you can see when you pass in const X it matches case 2 and if you pass in const X& it matches case 3.
Note: std::shared_ptr is the noise in this your sample, I remove it to demonstrate type deduction.
I have some cases of use of auto:
auto s = expr; //s is always lvalue
auto & s = expr; //s is always lvalue reference? What if expr is rvalue?
auto && s = expr; //s is perfectly forwarded
Are they true? If not, why?
dyp is correct and I would like to elaborate.
First of all, the conclusion is the from dyp:
The type deduced for auto in the declaration of a variable is defined
via the rules of template argument deduction, see [dcl.spec.auto]/6;
with one exception: if the initializer is a braced-init-list, the
deduced type is a std::initializer_list.
I'll explain.
First,
auto s = expr;
This is same as deducing the T from expr,
template<class T>
void f(T s);
f(expr);
The rule for template argument deduction is quite complicated, since you are only concerning with the lvalue and rvalue stuff, let's focus on this.
Template argument deduction is by comparing the template parameter type (call it P, in this case P is T), and the corresponding argument (call it A, in this case, the type of expr).
From 14.8.2.1,
If P is not a reference type:
— If A is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2) is
used in place of A for type deduction; otherwise,
— If A is a function type, the pointer type produced by the function-to-pointer standard conversion (4.3)
is used in place of A for type deduction; otherwise,
— If A is a cv-qualified type, the top level cv-qualifiers of A’s type are ignored for type deduction.
So, if expr is array or function, it will be treated as pointers, if expr has cv-qualification (const etc), they will be ignored.
If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction.
This actually says:
const auto s = expr;
s is a const variable, but for type deduction for auto purposes, the const will be removed.
Thus, from the above rules, auto will be deduced to the type of expr (after some type conversion stated above).
Note that, when an expression is a reference to T, it will be adjusted to T before prior analysis.
So whatever expr is – rvalue, lvalue, or lvalue/rvalue ref type – the type of auto will always be the type of expr without reference.
auto s1 = 1; //int
int &x = s1;
auto s2 = x; //int
int &&y = 2;
auto s3 = y; //int
Second, let's look at
auto &s = expr;
This will be same as
template<class T>
void f(T &s);
f(expr);
The extra rule from standard is as follows:
If P is a reference type, the type referred to by P is used for type deduction.
So the deduction of auto will be exactly same as without &, but after the auto type is deducted, the & is added to the end of auto.
//auto &s1 = 1; //auto is deducted to int, int &s1 = 1, error!
const auto &s1 = 1; //auto is deducted to int, const int &s1 = 1; ok!
const int &x = s1;
auto &s2 = x; //auto is int, int &s2 = x; ok!
int &&y = 2;
auto &s3 = y; //auto is int, int &s3 = y; ok!
Note that the last y is an lvalue. The rule of C++ is: named rvalue reference is an lvalue.
Lastly:
auto &&s = expr;
This is no doubt same as
template<class T>
void f(T &&s);
f(expr);
One additional rule from standard:
If P is an rvalue reference to a cv-unqualified template parameter and
the argument is an lvalue, the type “lvalue reference to A” is used in
place of A for type deduction.
This actually says that, if expr is an rvalue, the rule will be same as the second case (lvalue case), but if expr is an lvalue, the type of A will be an lvalue reference to A.
Note from previous explained, A is never reference, because the type of an expression is never a reference. But for this special case (auto &&, and A is an lvalue), reference to A must be used, regardless expr itself is a reference type or not.
Example:
auto &&s1 = 1; //auto is deducted to int, int &&s1 = 1, ok!
int x = 1;
auto &&s2 = x; //x is lvalue, so A is int &, auto is deducted to int &, int & &&s2 = x; ok!
int &&y = 2;
auto &&s3 = y; //y is lvalue, auto is int &, int & &&s3 = y; ok!
Since no one gives an answer and I kinda understand now (thanks to #dyp), I would just post answer myself. Point out error if any:
auto s = expr;
If expr is lvalue, prvalue, or any reference, s is always a lvalue and a copy is made.
auto& s = expr;
If expr is lvalue, lvalue reference or rvalue reference, s is lvalue reference.
If expr is prvalue, it is an error and not legal.
auto&& s = expr;
s is perfectly forwarded (rvalue would become rvalue reference and collapse).
Let's say I have this function:
bool f(int&& one, int&& two) { }
If I attempt to call it with this code:
int x = 4;
f(x, 5);
the compiler will complain that it cannot convert x from lvalue reference to rvalue reference, which is correct.
Now if I convert f into a template function like this:
template <class T, class U>
bool f(T&& one, U&& two) { }
then I can call it with an lvalue reference:
int x = 5;
f(x, 5);
Why is it so? Why doesn't the compiler complain in this case?
Per § 8.3.3 / 6. It's the reference collapsing rule.
template <class T> void func(T&&) // Accepts rvalue or lvalue
void func(T&&) // Accepts rvalue only
void func(T&) // Accepts lvalue only
Worth example from standard draft:
int i;
typedef int& LRI;
typedef int&& RRI;
LRI& r1 = i; // r1 has the type int&
const LRI& r2 = i; // r2 has the type int&
const LRI&& r3 = i; // r3 has the type int&
RRI& r4 = i; // r4 has the type int&
RRI&& r5 = 5; // r5 has the type int&&
decltype(r2)& r6 = i; // r6 has the type int&
decltype(r2)&& r7 = i; // r7 has the type int&
Because there is a template argument deduction, reference collapsing happens. It is what Scott Meyers calls universal references. The U&& will actually become int &. There is a nice article and video about how it works and how it could be used.
This happens because of reference collapsing rules added in c++11
A& & becomes A&
A& && becomes A&
A&& & becomes A&
A&& && becomes A&&
In templates these rules are applied but not in a normal function there is no reference collapsing normally in function. There are some other specific situations where reference collapsing will occur like in the presence of auto, decltype or a typedef (that includes using declarations) That explains the results of your compilation. Reference collapsing had to be added in c++11 because otherwise using references like A & & would become errors since you cannot have a reference to a reference.