C++11 perfect forwarding and reference collapsing - c++

Consider this code:
template<typename T>
void foo(T&& param){ //In this case && is called universal reference
std:string tmp = std::forward<string>(param);
}
My question is if universal reference type can be deduced why do I still need to call forward ?
Why without forwarding tmp's correct c'tor won't be called even if T's type was deduced.
My second question is about reference collapsing rules:
A& && becomes A&
A&& && becomes A&&
so according this rules and taking in account universal reference why std::forward signature can't be as follows:
template<class T>
T&& forward(T&& arg){
return static_cast<T&&>(arg);
}
According to the rules from above if T's type is rvalue reference it will collapse to rvalue reference , if T's type is lvalue reference it will collapse to lvalue reference.
So why std::forward have two different signatures one for lvalue reference and one for rvalue reference am I missing something ?

My question is if universal reference type can be deduced why do I still need to call forward ?
Because as soon as you give a name to the parameter param it is an lvalue, even if the function was called with an rvalue, so it wouldn't be forwarded as an rvalue unless you use forward<T>
Why without forwarding tmp's correct c'tor won't be called even if T's type was deduced.
Because param is an lvalue. To restore the value category of the argument passed to foo you need to cast it back to string& or string&& which means you need to know the type T was deduced as, and use forward to do the cast.
So why std::forward have two different signatures one for lvalue reference and one for rvalue reference am I missing something ?
It was changed by http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3143.html
There is lots of background info in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3143.html and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2951.html
The problem with your suggested version is that if you say forward<string> then the parameter T is not deduced so doesn't function as a forwarding reference, which means that T&& can't bind to an lvalue, and it needs to be able to bind to an lvalue in order for forward<string>(param) to work, because param is an lvalue there.

Related

Understanding of the implementation of std::forward since C++11

(constexpr and noexcept are left out, since they seem irrelevant for the purpose of understanding how std::forward behaves.)
Based on my understanding of Scott Meyers' "Effective Modern C++",
a sample implementation of std::move in C++14 is the following
template<typename T>
decltype(auto) move(T&& param) {
return static_cast<remove_reference_t<T>&&>(param);
}
Given the explanation of what a forwarding (or "universal") reference is, this implementation, I think, is pretty clear to me:
the parameter param is of type T&&, i.e. an rvalue reference or lvalue reference (to whichever the type of the argument is), depending on whether the argument is an rvalue or lvalue in the caller; in other words param can bind both to rvalues and lvalues (i.e. anything); this is intended, since move should cast anything to rvalue.
decltype(auto) is just the concise way to express the return type based on the actual return statement.
the returned object is the same object param, casted to an rvalue reference (&&) to whatever the type T is, once its deduced referenceness is stripped off (the deduction is done on T&&, not on ⋯<T>&&).
In short, my understanding of the use of forwarding/universal references in the implementation of move is the following:
the forwarding/universal reference T&& is used for the parameter since it is intended to bind to anything;
the return type is an rvalue reference, since move is intended to turn anything to rvalue.
It'd be nice to know if my understanding is right so far.
On the other hand, a sample implementation of std::forward in C++14 is the following
template<typename T>
T&& forward(remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
My understanding is the following:
T&&, the return type, must be a forwarding/universal reference, since we want forward to return either by rvalue reference or by lvalue reference, hence type deduction takes place on the return type here (unlike what happens for move, where type deduction takes place on the parameter side) is an rvalue reference to whichever template type argument is passed to forward;
since T encodes the lvalue/rvalue-ness of the actual argument which binds the callers' parameter that is passed as argument to forward, T itself can result to be actual_type& or actual_type, hence T&& can be either an lvalue reference or rvalue reference.
The type of param is an lvalue reference to whatever the type T is, once its deduced referenceness is stripped off. Actually in std::forward type deduction is disabled on purpose, requiring that the template type argument be passed explicitly.
My doubts are the following.
The two instances of forward (two for each type on which is it called, actually) only differ for the return type (rvalue reference when an rvalue is passed, lvalue reference when an lvalue is passed), since in both cases param is of type lvalue reference to non-const reference-less T. Isn't the return type something which does not count in overload resolution? (Maybe I've used "overload" improperly, here.)
Since the type of param is non-const lvalue reference to reference-less T, and since an lvalue reference must be to-const in order to bind to an rvalue, how can param bind to an rvalue?
As a side question:
can decltype(auto) be used for the return type, as it is done for move?
forward is essentially a machinery to conserve the value category in perfect forwarding.
Consider a simple function that attempts to call the f function transparently, respecting value category.
template <class T>
decltype(auto) g(T&& arg)
{
return f(arg);
}
Here, the problem is that the expression arg is always an lvalue regardless of whether arg is of rvalue reference type. This is where forward comes in handy:
template <class T>
decltype(auto) g(T&& arg)
{
return f(forward<T>(arg));
}
Consider a reference implementation of std::forward:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept
{
return static_cast<T&&>(t);
}
template <class T>
constexpr T&& forward(remove_reference_t<T>&& t) noexcept
{
static_assert(!std::is_lvalue_reference_v<T>);
return static_cast<T&&>(t);
}
(You can use decltype(auto) here, because the deduced type will always be T&&.)
In all the following cases, the first overload is called because the expression arg denotes a variable and hence is an lvalue:
If g is called with a non-const lvalue, then T is deduced as a non-const lvalue reference type. T&& is the same as T, and forward<T>(arg) is a non-const lvalue expression. Therefore, f is called with a non-const lvalue expression.
If g is called with a const lvalue, then T is deduced as a const lvalue reference type. T&& is the same as T, and forward<T>(arg) is a const lvalue expression. Therefore, f is called with a const lvalue expression.
If g is called with an rvalue, then T is deduced as a non-reference type. T&& is an rvalue reference type, and forward<T>(arg) is an rvalue expression. Therefore, f is called with an rvalue expression.
In all cases, the value category is respected.
The second overload is not used in normal perfect forwarding. See What is the purpose of std::forward()'s rvalue reference overload? for its usage.

Does an rvalue keep its "status" when a const reference parameter binds to it?

Let T be an arbitrary type. Consider a function that takes a const [lvalue] reference:
void f(const T &obj);
Suppose that this function internally makes a call to another function, which has an rvalue reference overload:
void g(T &&obj);
If we pass an rvalue to f, will the rvalue reference overload of g be called, or will it fail to do so since it has been "converted"/bound to a const lvalue reference?
Similarly, if f called a function that takes an instance of T by value,
void h(T obj);
and T has a move constructor, (i.e. T(T &&);), will the move constructor be called, or will the copy constructor be called?
In conclusion, if we wanted to ensure that, when calling f on an rvalue, the rvalue reference is passed around keeping its rvalue "status", would we necessarily have to provide an rvalue reference overload for f?
Value categories are applied to expressions, not objects. obj in f is an lvalue expression, and will therefore be treated as such. Note that obj in g is also an lvalue expression; if the expression is a name for an object, then it's an lvalue.
The ability you're talking about is exactly why forwarding references exist: so that the copy/move aspects of an argument expression's value category can be preserved through function calls. f would have to become a template of the form template<typename T> void f(T&& t);, and you would have to use std::forward when passing it to g.
When you use the name of a reference as an expression, that expression is always an lvalue. No matter if it's a lvalue reference or an rvalue reference. It also doesn't matter if the reference was initialized with an lvalue or an rvalue.
int &&x = 1;
f(x); // Here `x` is lvalue.
So in void f(const T &obj) {...}, obj is always an lvalue, regardless of what you pass as an argument.
Also note that value category is determined at compile time. Since f is not a template, value category of every expression in it can't depend on arguments you pass.
Thus:
If we pass an rvalue to f, will the rvalue reference overload of g be called
No.
if f called a function that takes an instance of T by value, void h(T obj); and T has a move constructor, (i.e. T(T &&);), will the move constructor be called
No.
In conclusion, if we wanted to ensure that, when calling f on an rvalue, the rvalue reference is passed around keeping its rvalue "status", would we necessarily have to provide an rvalue reference overload for f?
Providing an overload is one option. Note that in this case you have to explicitly call std::move in the rvalue overload.
Another option is using forwarding references, as Nicol Bolas suggests:
template <typename T> void f(T &&t)
{
g(std::forward<T>(t));
}
Here, std::forward essentially acts as a 'conditional move'. It moves t if an rvalue was passed to it, and does nothing otherwise.

rvalue reference matching (perfect forwarding example)

I got confused by the following perfect forwarding function, where the template parameter T can match rvalue or lvalue references:
template<typename T>
void foo(T&& t){
T::A; // intended error to inspect type
}
int main(){
std::vector<int> a;
std::vector<int> && b = std::move(a);
foo(b); // T is std::vector<int> &
foo(std::move(a)); // T is std::vector<int>
}
I dont understand why the template argument deduction of T in foo is so different in these two cases? Whats the fundamental difference and important what is t's type in function foo.
std::move(a) returns a rvalue reference and b is already a rvalue reference (but has a name).
Is that right that, b s type is a rvalue reference to std::vector<int>, but as far as my understanding goes, it has a name and is thus considered an lvalue in function main?
Can anyone shine some light into this :-)
There is a special type deduction rule when && is used with templates.
template <class T>
void func(T&& t) {
}
"When && appears in a type-deducing context, T&& acquires a special
meaning. When func is instantiated, T depends on whether the argument
passed to func is an lvalue or an rvalue. If it's an lvalue of type U,
T is deduced to U&. If it's an rvalue, T is deduced to U:"
func(4); // 4 is an rvalue: T deduced to int
double d = 3.14;
func(d); // d is an lvalue; T deduced to double&
float f() {...}
func(f()); // f() is an rvalue; T deduced to float
int bar(int i) {
func(i); // i is an lvalue; T deduced to int&
}
Also, reference collapsing rule is a good read.
Check this out for a really good explanation:
perfect forwarding
If you think about the signature of your function, the type of the parameter is T&&. In your second example, T is deduced to vector<int>, that means that the type of the parameter to your function is vector<int>&&. So you are still passing by (rvalue) reference.
In the other case, you deduce T to vector<int>&. So the type of the argument is vector<int> & &&... or it would be, but references to references are not allowed. Reference collapsing takes over, and any double reference involving an lvalue reference become an lvalue reference. So you are passing by lvalue reference.
As far as b goes, this is a well known gotcha of rvalue references. Essentially, b's type is rvalue reference, but b itself still has a value category of lvalue. Think of it this way: b itself is a variable, that must live on the stack somewhere, and have an address. So it's an lvalue. This is precisely way calling std::forward when forwarding arguments is necessary. If you didn't do it, then they would always be forwarded as lvalue arguments.
I really recommend this Scott Meyers article: https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers. Read it carefully!
Is that right that, b s type is a rvalue reference to std::vector<int>, but as far as my understanding goes, it has a name and is thus considered an lvalue in function main?
Yes, that's exactly it. It makes more sense if you think about rvalue reference function parameters: the caller is specifying that the function can do whatever it wants with the objects it gets. So from inside the function body, in order to make sure the code really can do whatever it wants with it, the parameter should be treated as an lvalue. That same argument can also be made for other rvalue references, including the b in your example, albeit to a lesser extent.
The expressions a and b are both lvalues, and the expression std::move(a) is an rvalue.
The deduction for the parameter T makes use of special reference collapsing rules so that the type of t is either an lvalue or an rvalue reference as needed to bind to the function call argument.

Is it meaningless to declare the return type of a function as T&&?

As we have known, in most common cases, T&& means "this is a temporary object". However, if one wants to return a temporary object from a function, he/she can declare the function as follows:
template<class T>
T f()
{
T t;
......
return t;
}
or (Note: Not Correct)
template<class T>
T&& f()
{
T t;
......
return t;
}
But I think the latter is overdid, because the former is enough and backward compatible.
Yet, I also find the std::forward()'s return type is declared as T&&, so I'm sure my understanding about this is incomplete.
My real question is: When and where should we declare the return type of a function as T&&?
In your example, the T&& is wrong, it's a dangling reference.
But std::forward doesn't return an rvalue reference to a local variable in its own definition, it returns an rvalue reference to its by-rvalue-reference argument (or an lvalue reference to a by-lvalue-reference argument).
You should return an rvalue reference only if you want the caller of your function to be able to move from whatever that reference refers to.
Normally that will only be if the purpose of the function is to provide move access to some significant object (perhaps which already exists). So that includes std::move (which allows you to move from an lvalue), and similarly you might write an accessor function specifically designed for users to move from a data member of some object, or an element of some container. If the object itself isn't significant, only the value, then you can return by value.
As grizzly says, sometimes due to reference collapsing you can take advantage of tricks which mean that you type T&& in your code, but when T is already an lvalue-reference type T&& is the same lvalue reference type. std::forward uses that trick. That is to say, because of reference collapsing T&& doesn't mean "rvalue reference to T", it means "T if T is a reference type, otherwise rvalue reference to T".
T&& doesn't necessarily mean that the result is an rvalue. When used with a template parameter && denotes a universal reference, which can either be an rvalue or an lvalue reference. More specifically: If T is an lvalue reference type foo&, T&& is actually an lvalue refere to foo, otherwise it denotes an rvalue reference.
This can be used to write functions, which take any kind of argument:
template<typename T> void foo(T&&);
bar a;
const bar b;
foo(a);//T and T&& will both be bar&
foo(b);//T and T&& will both be const bar&
foo(bar());//T will be bar, T&& will be bar&&
With this in mind std::forward is called with a T& and casts it to T&&, where T is explicitly stated. So if the original function parameter was an lvalue reference, it will return one, otherwise it will return an rvalue reference, enabling perfect forwarding.
As for when to use it as a return type: Rarely, though it might be useful to avoid copies when arguments are passed through, for example:
template<typename T> T&& foo(T&& bar) {/*some ops*/ return std::forward<T>(bar);}
The answer depends on whether your function is a template function or not. In your question it was, but let's first take a look at if it isn't:
Non-Template
T&& is not meaningless as a return type. The reason is that it, in fact, does not mean "temporary object". It means "rvalue reference". The difference is subtle, but can be highlighted with a class of functions for which this return type is relevant. Namely, what if we want to return a reference, to an rvalue, but the object it refers to is not a local object of our function?
T&& return_rvalue(/*some data*/)
{
T&& t = Get_a_reference();
// Do something fascinating.
return static_cast<T&&>(t);
}
One very special case of this pattern is the function std::move, which takes any reference and returns it a corresponding rvalue reference. Naturally, in real code, you should of course use std::move rather than performing the cast directly, since this more clearly shows your intention.
Template
If T is a template parameter, T&& is what Scott Meyers refers to as a Universal Reference. This means the type of T&& will be figured out using reference collapsing... In short, it means T&& is an rvalue if T i not a reference type, and an lvalue reference if it is..

C++11 `T&&` parameter losing its `&&` correct terminology?

The following C++11 code does not compile:
struct T {};
void f(T&&) { }
void g(T&& t) { f(t); }
int main()
{
g(T());
}
The correct way to do this is:
void g(T&& t) { f(move(t)); }
This is very difficult to explain in the correct natural language terminology. The parameter t seems to lose its "&&" status which it needs to have reinstated with the std::move.
What do you call the T() in g(T()) ?
What do you call the T&& in g(T&& t) ?
What do you call the t in g(T&& t) ?
What do you call the t in f(t) and f(move(t)) ?
What do you call the return value of move(t)?
What do you call the overall effect?
Which section(s) of the standard deal with this issue?
The key point is that a parameter T&& b can only bind to an rvalue, but when referred to later the expression b is an lvalue.
So the argument to the function must be an rvalue, but inside the function body the parameter is an lvalue because by then you've bound a reference to it and given it a name and it's no longer an unnamed temporary.
An expression has a type (e.g. int, string etc.) and it has a value category (e.g. lvalue or rvalue) and these two things are distinct.
A named variable which is declared as T&& b has type "rvalue reference to T" and can only be bound to an rvalue, but when you later use that reference the expression b has value category "lvalue", because it has a name and refers to some object (whatever the reference is bound to, even though that was an rvalue.) This means to pass b to another function which takes an rvalue you can't just say f(b) because b is an lvalue, so you must convert it (back) to an rvalue, via std::move(b).
All parameters are lvalues, even if their type is "rvalue reference". They have names, so you can refer to them as often as you want. If named rvalue references were rvalues, you would get surprising behavior. We do not want implicit moves from lvalues, that's why you have to explicitly write std::move.
What do you call the T() in g(T()) ?
A temporary (which is movable).
What do you call the T&& in g(T&& t) ?
T&& is an r-value reference and represent an object that can be moved.
What do you call the t in g(T&& t) ?
t is actually an l-value since you can refer to it by name.
What do you call the t in f(t) and f(move(t)) ?
l-value
l-value being converted into an r-value reference when returned by move()
What do you call the return value of move(t)?
An r-value reference
As a note; you should maybe call the struct C, and write a separate example where T is actually templetized. The code needs to be different then, because in a function template< typename T > void f( T&& t ); you cannot simply use std::move() without being very careful, since T can actually be a const&, in which case you must not use std::move() but instead use perfect forwarding with std::forward< T >( t )
What do you call the T() in g(T()) ?
That is a temporary object and an r-value.
What do you call the T&& in g(T&& t) ?
You call that a r-value reference.
The reason why
void g(T&& t) { f(t); }
doesn't work is because an r-value reference can't bind to a named object (even if that named object happens to be another r-value reference).
The parameter t does not lose its "status". Simply, the parameter t is an lvalue, even though it is an rvalue reference. Keep in mind that lvalueness and rvalueness are orthogonal concepts and apply to values as to value references (including rvalue references). Thus, an rvalue reference can be either an lvalue or an rvalue. If it has a name, as in your example, it is an lvalue. This makes the type system orthogonal and it is a good feature IMHO.