Moving const and overloaded Universal Reference in C++ - c++

I am watching Scott Meyer's video "The Universal Reference/Overloading Collision Conundrum", where he gives an example of what not to do:
class MessedUp {
public:
template<typename T>
void doWork(const T& param) { std::cout << "doWork(const T& param)" << std::endl; }
template<typename T>
void doWork(T&& param) { std::cout << "doWork(T&& param)" << std::endl; }
};
.... //somewhere in the main
MessedUp m;
int w = 10;
const int cw = 20;
m.doWork(cw); // calls doWork(const T& param) as expected
m.doWork(std::move(cw)); // Calls doWork(T&& param)
I am curious as to why compiler chose doWork(T&& param) rather than doWork(const T& param) during Template Overload resolution. As far as I know, const objects can't be moved.

After template type deduction and substitution, the two overloads become:
//template<typename T>
void doWork(const int& param) { std::cout << "doWork(const T& param)" << std::endl; }
//template<typename T>
void doWork(const int&& param) { std::cout << "doWork(T&& param)" << std::endl; }
Note how T in the second overload has been deduced to const int.
Now, what happens is normal overload resolution: We compare the implicit conversion sequences required to convert the argument expression std::move(cw) (which is an xvalue of type const int) to the parameter types. Both rank as Exact Matches, so we have to look at the tie-breakers in [over.ics.rank]/3 and compare the two implicit conversion sequences S1 and S2 (a reference binding here is a conversion sequence):
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
[...]
S1 and S2 are reference bindings [...], and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.
As an xvalue is an rvalue (and a glvalue), this point applies, and the second overload is chosen.

&& does not mean move, it means rvalue reference. rvalue references will only bind to temporary(anonymous) objects, or objects cast to appear to be temporary objects by functions like std::move or std::forward, or objects automatically marked by the compiler to be temporary like locals returned from function on simple return X; lines.
You can have an rvalue reference to a const object. When this happens, you cannot move (unless mutable state can be moved), but it is still an rvalue reference.
Now, T&& can bind to an lvalue reference if T is an lvalue reference, so in type deduction context T&& can be called a universal reference. So one of the real problems with the above design is that m.doWork(w) will also bind to T&& with T=int&.
In overload resolution, a function that takes a template<typename T> void foo(T&&) with T=foo& be considered to be a worse match than template<typename T> void foo(T&) with T=foo if everything else is equal: but in your case, there is no T such that foo(T const&) is a foo(T const&&).

&& are useful to determine temporary rvalues from non-rvalue. So, you can steal the resource safely.
When you use std::move it casts the type to a rvalue and compiler will uses && overload.

What is happening is that doWork(T&& param) is being called with T = const int, because that is a perfect match (instead of a conversion to lvalue).
If you had trued to move the object, it would indeed have failed because const objects can't be moved.

Related

Forwarding reference and argument deduction

I'm trying to understand perfect forwarding a bit deeply and faced a question I can't figure out myself.
Suppose this code:
void fun(int& i) {
std::cout << "int&" << std::endl;
}
void fun(int&& i) {
std::cout << "int&&" << std::endl;
}
template <typename T>
void wrapper(T&& i) {
fun(i);
}
int main()
{
wrapper(4);
}
It prints int&. To fix this one should use std::forward. That's clear. What is unclear is why it is so.
What the code above unwraps into is:
void fun(int & i)
{
std::operator<<(std::cout, "int&").operator<<(std::endl);
}
void fun(int && i)
{
std::operator<<(std::cout, "int&&").operator<<(std::endl);
}
template <typename T>
void wrapper(T&& i) {
fun(i);
}
/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void wrapper<int>(int && i)
{
fun(i);
}
#endif
int main()
{
wrapper(4);
return 0;
}
So i should have rvalue type of int&&. The question is: why do I need std::forward here since compiler already knows that i is int&& not int& but still calls fun(it&)?
Types and value categories are different things.
Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category.
i, the name of the variable, is an lvalue expression, even the variable's type is rvalue-reference.
The following expressions are lvalue expressions:
the name of a variable, ... Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;
...
That's why we should use std::forward to preserve the original value category of a forwarding reference argument.
why do I need std::forward here since compiler already knows that i is
int&& not int& but still calls fun(it&)?
The type of i is int&&, but i itself is an lvalue. So when you're calling fun(i), since i itself is an lvalue, the compiler will choose fun(int &).
If you want to invoke fun(int &&), you can use std::move to cast it to an rvalue
fun(std::move(i));
why do I need std::forward here since compiler already knows that i is int&& not int& but still calls fun(it&)?
Because i when used as/in an expression such as the call fun(i) is an lvalue. That is the value category of i when used as/in an expression is lvalue. Thus the call fun(i) selects the first overload(void fun(int&)).
On the other hand, the declared type of i is int&& i.e., an rvalue reference to int.

Second overload of std::foward (example on cppreference.com)

I know that the second overload of std::forward:
template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;
is used for rvalues (as stated by Howard Hinnant in his answer: How does std::forward receive the correct argument?)
There is an example of when this overload is used at cppreference.com (that is also mentioned in How does std::forward receive the correct argument? by Praetorian):
Forwards rvalues as rvalues and prohibits forwarding of rvalues as lvalues This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
For example, if a wrapper does not just forward its argument, but calls a member function on the argument, and forwards its result:
// transforming wrapper
template<class T>
void wrapper(T&& arg)
{
foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
}
where the type of arg may be
struct Arg
{
int i = 1;
int get() && { return i; } // call to this overload is rvalue
int& get() & { return i; } // call to this overload is lvalue
};
I really don't get this example. Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
Cppreference states:
This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
As an example:
void func(int& lvalue)
{
std::cout << "I got an lvalue!" << std::endl;
}
void func(int&& rvalue)
{
std::cout << "I got an rvalue!" << std::endl;
}
template <typename T>
T&& myForward(typename std::remove_reference_t<T>& t)
{
return static_cast<T&&>(t);
}
struct foo
{
int i = 42;
int& get()& { return i; }
int get()&& { return i; }
};
template <typename T>
void wrapper(T&& t)
{
func(myForward<T>(t).get());
}
int main()
{
foo f;
wrapper(f);
wrapper(foo());
return 0;
}
This prints:
I got an lvalue!
I got an rvalue!
just fine, without the outer forward, while it also forwards the "result of an expression [...] as the original value category of a forwarding reference argument." It does not even need the second overload of std::forward. This overload is only necessary when calling func() like this:
func(myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get()));
Still, I can't wrap my head around why anyone would need to add the outer forward.
Edit: Edit moved to follow-up question: RValue-reference overload of std::forward potentially causing dangling reference?
Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
It's not. The expression already is of its own correct value category. In C++17 (when returning by value bigger types) it's even a pessimization. All it does is turn a potential prvalue into an xvalue, and inhibiting copy elision. I'm tempted to say it's cargo cult programming.

Why doesn't my forward_ function work for rvalues?

I've understood how std::move works and implemented my own version for practice only. Now I'm trying to understand how std::forward works:
I've implemented this so far:
#include <iostream>
template <typename T>
T&& forward_(T&& x)
{
return static_cast<T&&>(x);
}
/*template <typename T>
T&& forward_(T& x)
{
return static_cast<T&&>(x);
}*/
void incr(int& i)
{
++i;
}
void incr2(int x)
{
++x;
}
void incr3(int&& x)
{
++x;
}
template <typename T, typename F>
void call(T&& a, F func)
{
func(forward_<T>(a));
}
int main()
{
int i = 10;
std::cout << i << '\n';
call(i, incr);
std::cout << i << '\n';
call(i, incr2);
std::cout << i << '\n';
call(0, incr3); // Error: cannot bind rvalue reference of type int&& to lvalue of type int.
std::cout << "\ndone!\n";
}
Why must I provide the overloaded forward(T&) version taking an lvalue reference? As I understand it a forwarding reference can yield an lvalue or an rvalue depending on the type of its argument. So passing the prvalue literal 0 to call along with the incr3 function that takes an rvalue reference of type int&& normally doesn't need forward<T>(T&)?!
If I un-comment the forward_(T&) version it works fine!?
I'm still confused about: why if I only use the forward_(T&) version does it work for any value category? Then what is the point in having the one taking a forwarding reference forward_(T&&)?
If I un-comment the version taking lvalue reference to T& and the one taking forwarding reference T&& then the code works fine and I've added some messages inside both to check which one called. the result is the the one with T&& never called!
template <typename T>
T&& forward_(T& x)
{
std::cout << "forward_(T&)\n";
return static_cast<T&&>(x);
}
template <typename T>
T&& forward_(T&& x)
{
std::cout << "forward_(T&&)\n";
return static_cast<T&&>(x);
}
I mean running the same code in the driver program I've shown above.
A T&& reference stops being a forwarding reference if you manually specify T (instead of letting the compiler deduce it). If the T is not an lvalue reference, then T&& is an rvalue reference and won't accept lvalues.
For example, if you do forward_<int>(...), then the parameter is an rvalue reference and ... can only be an rvalue.
But if you do forward_(...), then the parameter is a forwarding reference and ... can have any value category. (Calling it like this makes no sense though, since forward_(x) will have the same value category as x itself.)
It is clear that you wander why having two versions of std::forward; one takes an l-value reference to the type parameter T& and the other takes a universal reference (forwarding) to the type parameter. T&&.
In your case you are using forward_ from inside the function template call which has forwarding reference too. The problem is that even that function call called with an rvalue it always uses forward_ for an lvalue because there's no way that call can pass its arguments without an object (parameter). Remember that a name of an object is an lvlaue even if it's initialized from an r-value. That is why always in your example forward_(T&) is called.
Now you ask why there's second version taking forwarding reference?
It is so simple and as you may have already guessed: it is used for r-values (the values not the names of those objects).
Here is an example:
template <typename T>
T&& forward_(T& x)
{
std::cout << "forward_(T&)\n";
return static_cast<T&&>(x);
}
template <typename T>
T&& forward_(T&& x)
{
std::cout << "forward_(T&&)\n";
return static_cast<T&&>(x);
}
int main()
{
int i = 10;
forward_(i); // forward(T&) (1)
forward_(5); // forward(T&&) (2)
forward_("Hi"); // forward(T&) (3)
}

What is the use of forwarding an rvalue? [duplicate]

I know that the second overload of std::forward:
template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;
is used for rvalues (as stated by Howard Hinnant in his answer: How does std::forward receive the correct argument?)
There is an example of when this overload is used at cppreference.com (that is also mentioned in How does std::forward receive the correct argument? by Praetorian):
Forwards rvalues as rvalues and prohibits forwarding of rvalues as lvalues This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
For example, if a wrapper does not just forward its argument, but calls a member function on the argument, and forwards its result:
// transforming wrapper
template<class T>
void wrapper(T&& arg)
{
foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
}
where the type of arg may be
struct Arg
{
int i = 1;
int get() && { return i; } // call to this overload is rvalue
int& get() & { return i; } // call to this overload is lvalue
};
I really don't get this example. Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
Cppreference states:
This overload makes it possible to forward a result of an expression (such as function call), which may be rvalue or lvalue, as the original value category of a forwarding reference argument.
As an example:
void func(int& lvalue)
{
std::cout << "I got an lvalue!" << std::endl;
}
void func(int&& rvalue)
{
std::cout << "I got an rvalue!" << std::endl;
}
template <typename T>
T&& myForward(typename std::remove_reference_t<T>& t)
{
return static_cast<T&&>(t);
}
struct foo
{
int i = 42;
int& get()& { return i; }
int get()&& { return i; }
};
template <typename T>
void wrapper(T&& t)
{
func(myForward<T>(t).get());
}
int main()
{
foo f;
wrapper(f);
wrapper(foo());
return 0;
}
This prints:
I got an lvalue!
I got an rvalue!
just fine, without the outer forward, while it also forwards the "result of an expression [...] as the original value category of a forwarding reference argument." It does not even need the second overload of std::forward. This overload is only necessary when calling func() like this:
func(myForward<decltype(myForward<T>(t).get())>(myForward<T>(t).get()));
Still, I can't wrap my head around why anyone would need to add the outer forward.
Edit: Edit moved to follow-up question: RValue-reference overload of std::forward potentially causing dangling reference?
Why is the outer forward forward<decltype(forward<T>(arg).get())> even needed?
It's not. The expression already is of its own correct value category. In C++17 (when returning by value bigger types) it's even a pessimization. All it does is turn a potential prvalue into an xvalue, and inhibiting copy elision. I'm tempted to say it's cargo cult programming.

Why does std::forward return static_cast<T&&> and not static_cast<T>?

Let's have a function called Y that overloads:
void Y(int& lvalue)
{ cout << "lvalue!" << endl; }
void Y(int&& rvalue)
{ cout << "rvalue!" << endl; }
Now, let's define a template function that acts like std::forward
template<class T>
void f(T&& x)
{
Y( static_cast<T&&>(x) ); // Using static_cast<T&&>(x) like in std::forward
}
Now look at the main()
int main()
{
int i = 10;
f(i); // lvalue >> T = int&
f(10); // rvalue >> T = int&&
}
As expected, the output is
lvalue!
rvalue!
Now come back to the template function f() and replace static_cast<T&&>(x) with static_cast<T>(x). Let's see the output:
lvalue!
rvalue!
It's the same! Why? If they are the same, then why std::forward<> returns a cast from x to T&&?
The lvalue vs rvalue classification remains the same, but the effect is quite different (and the value category does change - although not in an observable way in your example). Let's go over the four cases:
template<class T>
void f(T&& x)
{
Y(static_cast<T&&>(x));
}
template<class T>
void g(T&& x)
{
Y(static_cast<T>(x));
}
If we call f with an lvalue, T will deduce as some X&, so the cast reference collapses X& && ==> X&, so we end up with the same lvalue and nothing changes.
If we call f with an rvalue, T will deduce as some X so the cast just converts x to an rvalue reference to x, so it becomes an rvalue (specifically, an xvalue).
If we call g with an lvalue, all the same things happen. There's no reference collapsing necessary, since we're just using T == X&, but the cast is still a no-op and we still end up with the same lvalue.
But if we call g with an rvalue, we have static_cast<T>(x) which will copy x. That copy is an rvalue (as your test verifies - except now it's a prvalue instead of an xvalue), but it's an extra, unnecessary copy at best and would be a compilation failure (if T is movable but noncopyable) at worst. With static_cast<T&&>(x), we were casting to a reference, which doesn't invoke a copy.
So that's why we do T&&.