Why do we need reference collapsing rules [duplicate]
My question is why do we need these rules if T is already deduced to int&&?
This is not quite true. The rules for type deduction won't deduce the argument to be a reference. That is, in:
template <typename T>
void f(T);
And the expressions:
X g();
X& h();
X a;
f(g()); // argument is an rvalue, cannot be bound by lvalue-ref
f(h()); // argument is an lvalue
f(a); // argument is an lvalue
The deduced type will be X
in last two cases and it will fail to compile in the first. The type deduced will be the value type, not a reference type.
The next step is to figure out what the deduced type would be if the template took the argument by lvalue or rvalue reference. In the case of lvalue references, the options are clear, with a modified f
:
template <typename T>
void f(T &);
f(g()); // only const& can bind an rvalue: f(const X&), T == const int
f(h()); // f(X&)
f(a); // f(X&)
Up to here it was already defined in the previous version of the standard. Now the question is what should the deduced types be if the template takes an rvalue-references. This is what was added in C++11. Consider now:
template <typename T>
void f(T &&);
And rvalue will only bind to an rvalue, and never to an lvalue. This would imply that using the same simple rules as for lvalue-references (what type T would make the call compile) the second and third calls would not compile:
f(g()); // Fine, and rvalue-reference binds the rvalue
f(h()); // an rvalue-reference cannot bind an lvalue!
f(a); // an rvalue-reference cannot bind an lvalue!
Without the reference collapsing rules, the user would have to provide two overloads for the template, one that takes an rvalue-reference, another that takes an lvalue-reference. The problem is that as the number of arguments increases the number of alternatives grows exponentially, and implementing perfect forwarding becomes almost as hard in C++03 (with the only advantage of being able to detect an rvalue with an rvalue-reference).
So something different needs to be done, and that is reference collapsing, which are really a way of describing the desired semantics. A different way of describing them is that when you type &&
by a template argument you don't really ask for an rvalue-reference, as that would not allow the call with an lvalue, but you are rather asking the compiler to give you the best type of reference matching.
Concise explanation of reference collapsing rules requested: (1) A& & - A& , (2) A& && - A& , (3) A&& & - A& , and (4) A&& && - A&&
The reference collapsing rules (save for A& & -> A&
, which is C++98/03) exist for one reason: to allow perfect forwarding to work.
"Perfect" forwarding means to effectively forward parameters as if the user had called the function directly (minus elision, which is broken by forwarding). There are three kinds of values the user could pass: lvalues, xvalues, and prvalues, and there are three ways that the receiving location can take a value: by value, by (possibly const) lvalue reference, and by (possibly const) rvalue reference.
Consider this function:
template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }
By value
If Call
takes its parameter by value, then a copy/move must happen into that parameter. Which one depends on what the incoming value is. If the incoming value is an lvalue, then it must copy the lvalue. If the incoming value is an rvalue (which collectively are xvalues and prvalues), then it must move from it.
If you call Fwd
with an lvalue, C++'s type-deduction rules mean that T
will be deduced as Type&
, where Type
is the type of the lvalue. Obviously if the lvalue is const
, it will be deduced as const Type&
. The reference collapsing rules mean that Type & &&
becomes Type &
for v
, an lvalue reference. Which is exactly what we need to call Call
. Calling it with an lvalue reference will force a copy, exactly as if we had called it directly.
If you call Fwd
with an rvalue (ie: a Type
temporary expression or certain Type&&
expressions), then T
will be deduced as Type
. The reference collapsing rules give us Type &&
, which provokes a move/copy, which is almost exactly as if we had called it directly (minus elision).
By lvalue reference
If Call
takes its value by lvalue reference, then it should only be callable when the user uses lvalue parameters. If it's a const-lvalue reference, then it can be callable by anything (lvalue, xvalue, prvalue).
If you call Fwd
with an lvalue, we again get Type&
as the type of v
. This will bind to a non-const lvalue reference. If we call it with a const lvalue, we get const Type&
, which will only bind to a const lvalue reference argument in Call
.
If you call Fwd
with an xvalue, we again get Type&&
as the type of v
. This will not allow you to call a function that takes a non-const lvalue, as an xvalue cannot bind to a non-const lvalue reference. It can bind to a const lvalue reference, so if Call
used a const&
, we could call Fwd
with an xvalue.
If you call Fwd
with a prvalue, we again get Type&&
, so everything works as before. You cannot pass a temporary to a function that takes a non-const lvalue, so our forwarding function will likewise choke in the attempt to do so.
By rvalue reference
If Call
takes its value by rvalue reference, then it should only be callable when the user uses xvalue or rvalue parameters.
If you call Fwd
with an lvalue, we get Type&
. This will not bind to an rvalue reference parameter, so a compile error results. A const Type&
also won't bind to an rvalue reference parameter, so it still fails. And this is exactly what would happen if we called Call
directly with an lvalue.
If you call Fwd
with an xvalue, we get Type&&
, which works (cv-qualification still matters of course).
The same goes for using a prvalue.
std::forward
std::forward itself uses reference collapsing rules in a similar way, so as to pass incoming rvalue references as xvalues (function return values that are Type&&
are xvalues) and incoming lvalue references as lvalues (returning Type&
).
Cast to reference type
Is conversion to a reference even possible?
Yes, depending on the object you begin with.
Most simple example is one lvalue reference converted to another. This can even be an added cv-qualifier (or removed, with const_cast
), such as:
int const & foo(int & i) {
return static_cast<int const &>(i);
}
the derived-to-base casts and base-to-derived (dynamic) casts mentioned in the other answers are other such examples.
A more interesting example is the std::reference_wrapper
, which is an object you can receive as an rvalue, and cast it to an lvalue reference of the contained type:
int & foo(std::reference_wrapper<int> i) {
return static_cast<int &>(i);
}
Note that the cast above happens implicitly (I could've used return i
), but in some contexts (e.g. capturing variables with auto
type) you might want to write the cast explicitly.
What is meant in C++ Primer is simply that the behavior of casts in these examples, and others, is basically what you would expect - the result of a cast to a reference type is an lvalue.
why not introduce autoval/autoref or decltype_val/decltype_ref to C++?
auto
behaves like function template argument deduction : it strips away const
, volatile
and references, and uses reference collapsing rules. Thus :
auto i = x
is always a value;auto &i = x
is always a lvalue reference;auto &&i = x
is always either an lvalue or a rvalue reference, depending onx
.
decltype
has no deduction or type adjustment whatsoever : it is always the type of its operand, const
and references and all. It does have the "gotcha" that :
decltype(x)
yields the exact type ofx
, whereasdecltype((x))
yields the type of the expression(x)
, which forms a reference tox
.
C++ why does passing an lvalue to a move constructor work for templates?
Yes. In the case of a template function, the compiler deduces the template argument T
such that it matches the argument given.
Since someData
is in fact an lvalue, T
is deduced as SomeData &
. The declaration of Function
, after type deduction, then becomes
void Function(SomeData & &&)
and SomeData & &&
, following the rules for reference collapsing, becomes SomeData &
.
Hence, the function argument someData
becomes an lvalue-reference and is passed as such to the initialization of localData
. Note that (as @aschepler pointed out correctly) localData
is declared as T
, so it is itself a reference of type SomeData &
. Hence, no copy construction happens here – just the initialization of a reference.
If you want localData
to be an actual copy, you would have to turn the type from SomeData &
into SomeData
, i.e. you would have to remove the &
from the type. You could do this using std::remove_reference
:
template<class T>
void Function(T &&someData)
{
/* Create a copy, rather than initialize a reference: */
typename std::remove_reference<T>::type localData(someData);
localData.Print();
}
(For this, you'd have to #include <type_traits>
.)
What is the type of a reference of a reference in a template class [duplicate]
T
is const int&
because that's what you told it to be.
T&
is also const int&
because reference collapsing transforms your T&
into T
:
[dcl.ref]/6:
If a typedef-name ([dcl.typedef], [temp.param]) or a decltype-specifier ([dcl.type.simple]) denotes a typeTR
that is a reference to a typeT
, an attempt to create the type “lvalue reference to cvTR
” creates the type “lvalue reference toT
”, while an attempt to create the type “rvalue reference to cvTR
” creates the typeTR
. [ Note: This rule is known as reference collapsing. — end note ] [ Example: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&
— end example ]
This is for convenience, because there is no such thing as const int& &
(reference to reference; not to be confused with rvalue reference type const int&&
which does exist!) and it's handy to be able to just write code like yours without having to manually "get rid of" the "extra" &
.
The rationale behind this rule is explained in more detail here:
- Concise explanation of reference collapsing rules requested: (1) A& & -> A& , (2) A& && -> A& , (3) A&& & -> A& , and (4) A&& && -> A&&
Confusion about the return type of std::get() on std::tuple objects
Look at signature of std::get
:
template< std::size_t I, class... Types >
constexpr std::tuple_element_t<I, tuple<Types...> >&
get( tuple<Types...>& t )
template< std::size_t I, class... Types >
constexpr std::tuple_element_t<I, tuple<Types...> >&&
get( tuple<Types...>&& t )
In your case t
is a l-value, so it return int&& &
which became int&
.
Is it specified in the C++11 standard that std::begin(Container&&) returns const_iterator?
Let's try to analyze what happens, step by step:
You're calling
std::begin(std::vector<int>&&)
, butstd::begin
has no overload that takes an rvalue:template< class C >
auto begin( C& c ) -> decltype(c.begin());
template< class C >
auto begin( const C& c ) -> decltype(c.begin());
Due to reference collapsing, a temporary (xvalue) will only bind to a
const
lvalue reference:If you call Fwd with an xvalue, we again get Type&& as the type of v. This will not allow you to call a function that takes a non-const lvalue, as an xvalue cannot bind to a non-const lvalue reference. It can bind to a const lvalue reference, so if Call used a const&, we could call Fwd with an xvalue.
(From the linked answer).
Therefore, the
template<class C> auto begin(const C& c) -> decltype(c.begin());
overload is being called, which returns a
const
iterator.Why?
Because
std::begin(v)
callsv.begin()
, which returns aconst_iterator
when called onconst
instances ofstd::vector
.
Related Topics
How to Determine Opencv Version
How to Have All the Inputs on the Same Line C++
Count How Many Times Elements in an Array Are Repeated
Fastest Way to Check If a File Exists Using Standard C++/C++11,14,17/C
How to Align Text to the Right Using Cout
Checking the Neighbour Values of Arrays
How to Read in User Entered Comma Separated Integers
Balanced Array Index While Summing an Array from Left and Right
How to Cast Const Uint8_T* to Char*
How to Determine the Boost Version on a System
Stack, Static, and Heap in C++
Initialization of All Elements of an Array to One Default Value in C++
A Positive Lambda: '+[]{}' - What Sorcery Is This