Why named variable calls are resolved to T&& instead of const T&? - c++

As the title says, why named variables calls are resolved to T&& instead of const T& functions?
#include <iostream>
template<typename T>
void f(T&& v)
{
std::cout << "void f(T&& v)" << std::endl;
}
template<typename T>
void f(const T& v)
{
std::cout << "void f(const T& v)" << std::endl;
}
int main()
{
int i = 0;
f(1);
f(i);
}
In this case both calls are resolved to first version of f(), even if i is named. One solution would be to add also:
template<typename T>
void f(T& v)
{
std::cout << "void f(T& v)" << std::endl;
}
or to change first version to:
template<typename T>
typename std::enable_if<!std::is_reference<T>::value, void>::type f(T&& v)
{
std::cout << "void f(T&& v)" << std::endl;
}
but I want to understand the reasons behind this decision.

The deduction is T = int &, which means f(T &&) == f(int &). The overload resolution rules ([over.ics.rank/13.3.3.2]) say that this is is a strictly better match than f(int const &). Both are classified as an "exact match" (binding a value to a reference), but the less CV-qualified reference is preferred.

A less standardese-encumbered answer would be to recognise that in the declaration:
template<typename T>
void f(T&& v)
Because T is a deduced type, T&& v is what Scott Meyers calls a universal reference, which can bind to both rvalues and lvalues. T can be deduced to be a reference type, so you are actually calling f<int&>(int& && v), at which point the reference-collapsing rules come into effect, making the nominal signature of this function f<int&>(int& v) which, as the previous answer noted, is a better match for a non-const int argument.
In Meyers' upcoming Effective Modern C++ he has the following items:
Item 30: Pass and return rvalue references via std::move, universal references via std::forward.
Item 31: Avoid overloading on universal references.
Item 31 tells you what not to do. Item 30 suggests what you could do instead, write only one overload of f, and perfectly forward v with std::forward:
template<typename T>
void f(T&& v)
{
std::cout << "void f(T&& v)" << std::endl;
if (std::is_rvalue_reference<decltype(v)>::value)
std::cout << "rvalue" << std::endl;
else
std::cout << "lvalue" << std::endl;
std::cout << "v = " << std::forward<T>(v) << std::endl;
}

Related

Why is std::forward necessary with forwarding references [duplicate]

This question already has answers here:
What are the main purposes of std::forward and which problems does it solve?
(7 answers)
Closed 6 years ago.
In a function template like this
template <typename T>
void foo(T&& x) {
bar(std::forward<T>(x));
}
Isn't x an rvalue reference inside foo, if foo is called with an rvalue reference? If foo is called with an lvalue reference, the cast isn't necessary anyway, because x will also be an lvalue reference inside of foo. Also T will be deduced to the lvalue reference type, and so std::forward<T> won't change the type of x.
I conducted a test using boost::typeindex and I get exactly the same types with and without std::forward<T>.
#include <iostream>
#include <utility>
#include <boost/type_index.hpp>
using std::cout;
using std::endl;
template <typename T> struct __ { };
template <typename T> struct prt_type { };
template <typename T>
std::ostream& operator<<(std::ostream& os, prt_type<T>) {
os << "\033[1;35m" << boost::typeindex::type_id<T>().pretty_name()
<< "\033[0m";
return os;
}
template <typename T>
void foo(T&& x) {
cout << prt_type<__<T>>{} << endl;
cout << prt_type<__<decltype(x)>>{} << endl;
cout << prt_type<__<decltype(std::forward<T>(x))>>{} << endl;
cout << endl;
}
int main(int argc, char* argv[])
{
foo(1);
int i = 2;
foo (i);
const int j = 3;
foo(j);
foo(std::move(i));
return 0;
}
The output of g++ -Wall test.cc && ./a.out with gcc 6.2.0 and boost 1.62.0 is
__<int>
__<int&&>
__<int&&>
__<int&>
__<int&>
__<int&>
__<int const&>
__<int const&>
__<int const&>
__<int>
__<int&&>
__<int&&>
Edit: I found this answer: https://stackoverflow.com/a/27409428/2640636 Apparently,
as soon as you give a name to the parameter it is an lvalue.
My question is then, why was this behavior chosen over keeping rvalue references as rvalues even when they are given names? It seems to me that the whole forwarding ordeal could be circumvented that way.
Edit2: I'm not asking about what std::forward does. I'm asking about why it's needed.
Isn't x an rvalue reference inside foo ?
No, x is a lvalue inside foo (it has a name and an address) of type rvalue reference. Combine that with reference collapsing rules and template type deduction rules and you'll see that you need std::forward to get the right reference type.
Basically, if what you pass to as x is a lvalue, say an int, then T is deduced as int&. Then int && & becomes int& (due to reference collapsing rules), i.e. a lvalue ref.
On the other hand, if you pass a rvalue, say 42, then T is deduced as int, so at the end you have an int&& as the type of x, i.e. a rvalue. Basically that's what std::forward does: casts to T&& the result, like a
static_cast<T&&>(x)
which becomes either T&& or T& due reference collapsing rules.
Its usefulness becomes obvious in generic code, where you may not know in advance whether you'll get a rvalue or lvalue. If you don't invoke std::forward and only do f(x), then x will always be a lvalue, so you'll be losing move semantics when needed and may end up with un-necessary copies etc.
Simple example where you can see the difference:
#include <iostream>
struct X
{
X() = default;
X(X&&) {std::cout << "Moving...\n";};
X(const X&) {std::cout << "Copying...\n";}
};
template <typename T>
void f1(T&& x)
{
g(std::forward<T>(x));
}
template <typename T>
void f2(T&& x)
{
g(x);
}
template <typename T>
void g(T x)
{ }
int main()
{
X x;
std::cout << "with std::forward\n";
f1(X{}); // moving
std::cout << "without std::forward\n";
f2(X{}); // copying
}
Live on Coliru
You really don't want your parameters to be automatically moved to the functions called. Consider this function:
template <typename T>
void foo(T&& x) {
bar(x);
baz(x);
global::y = std::forward<T>(x);
}
Now you really don't want an automatic move to bar and an empty parameter to baz.
The current rules of requiring you to specify if and when to move or forward a parameter are not accidental.
I get exactly the same types with and without std::forward<T>
...no? Your own output proves you wrong:
__<int> // T
__<int&&> // decltype(x)
__<int&&> // std::forward<T>(x)
Without using std::forward<T> or decltype(x) you will get int instead of int&&. This may inadvertently fail to "propagate the rvalueness" of x - consider this example:
void foo(int&) { cout << "int&\n"; }
void foo(int&&) { cout << "int&&\n"; }
template <typename T>
void without_forward(T&& x)
{
foo(x);
// ^
// `x` is an lvalue!
}
template <typename T>
void with_forward(T&& x)
{
// `std::forward` casts `x` to `int&&`.
// vvvvvvvvvvvvvvvvvv
foo(std::forward<T>(x));
// ^
// `x` is an lvalue!
}
template <typename T>
void with_decltype_cast(T&& x)
{
// `decltype(x)` is `int&&`. `x` is casted to `int&&`.
// vvvvvvvvvvv
foo(decltype(x)(x));
// ^
// `x` is an lvalue!
}
int main()
{
without_forward(1); // prints "int&"
with_forward(1); // prints "int&&"
with_decltype_cast(1); // prints "int&&"
}
wandbox example
x being an r-value is NOT the same thing as x having an r-value-reference type.
R-value is a property of an expression, whereas r-value-reference is a property of its type.
If you actually try to pass a variable that is an r-value reference to a function, it is treated like an l-value. The decltype is misleading you. Try it and see:
#include <iostream>
#include <typeinfo>
using namespace std;
template<class T> struct wrap { };
template<class T>
void bar(T &&value) { std::cout << " vs. " << typeid(wrap<T>).name() << std::endl; }
template<class T>
void foo(T &&value) { std::cout << typeid(wrap<T>).name(); return bar(value); }
int main()
{
int i = 1;
foo(static_cast<int &>(i));
foo(static_cast<int const &>(i));
foo(static_cast<int &&>(i));
foo(static_cast<int const &&>(i));
}
Output:
4wrapIRiE   vs. 4wrapIRiE
4wrapIRKiE vs. 4wrapIRKiE
4wrapIiE     vs. 4wrapIRiE (these should match!)
4wrapIKiE   vs. 4wrapIRKiE (these should match!)

Why rvalue reference pass as lvalue reference?

pass() reference argument and pass it to reference, however a rvalue argument actually called the reference(int&) instead of reference(int &&), here is my code snippet:
#include <iostream>
#include <utility>
void reference(int& v) {
std::cout << "lvalue" << std::endl;
}
void reference(int&& v) {
std::cout << "rvalue" << std::endl;
}
template <typename T>
void pass(T&& v) {
reference(v);
}
int main() {
std::cout << "rvalue pass:";
pass(1);
std::cout << "lvalue pass:";
int p = 1;
pass(p);
return 0;
}
the output is:
rvalue pass:lvalue
lvalue pass:lvalue
For p it is easy to understand according to reference collapsing rule, but why the template function pass v to reference() as lvalue?
template <typename T>
void pass(T&& v) {
reference(v);
}
You are using a Forwarding reference here quite alright, but the fact that there is now a name v, it's considered an lvalue to an rvalue reference.
Simply put, anything that has a name is an lvalue. This is why Perfect Forwarding is needed, to get full semantics, use std::forward
template <typename T>
void pass(T&& v) {
reference(std::forward<T>(v));
}
What std::forward<T> does is simply to do something like this
template <typename T>
void pass(T&& v) {
reference(static_cast<T&&>(v));
}
See this;
Why the template function pass v to reference() as lvalue?
That's because v is an lvalue. Wait, what? v is an rvalue reference. The important thing is that it is a reference, and thus an lvalue. It doesn't matter that it only binds to rvalues.
If you want to keep the value category, you will have to do perfect forwarding. Perfect forwarding means that if you pass an rvalue (like in your case), the function will be called with an rvalue (instead of an lvalue):
template <typename T>
void pass(T&& v) {
reference(std::forward<T>(v)); //forward 'v' to 'reference'
}

std::forward with templated overloaded function

I've got no idea why compiler gives me warnings about template instantiations.
Thats a piece of code which runs just fine and outputs lvalue/rvalue properly:
//template<typename T>
void overloaded(const /*T*/std::string& in)
{
std::cout << "lvalue" << std::endl;
}
//template<typename T>
void overloaded(/*T*/std::string&& in)
{
std::cout << "rvalue" << std::endl;
}
template<typename T>
void pass(T&& in)
{
overloaded(std::forward<T>(in));
}
int main()
{
std::string a;
pass(a);
pass(std::move(a));
getchar();
}
But i need to use it with templated type. So modifying the "overloaded" functions to
template<typename T>
void overloaded(const T& in)
{
std::cout << "lvalue" << std::endl;
}
template<typename T>
void overloaded(T&& in)
{
std::cout << "rvalue" << std::endl;
}
Gives template instantiations warnings, (when to me its clear T should be std::string), and console outputs rvalue 2 times instead of lvalue first.
What am i doing wrong?
Templates such as T&& are special. They are called "forwarding references". They have special deduction rules for functions like:
template<typename T>
void overloaded(T&& in)
Assume for a moment that overloaded is not overloaded. If you pass an lvalue expression of type std::string to overloaded, T will deduce as std::string&. If you pass an rvalue expression of type std::string to overloaded, T will deduce to std::string. You can use this knowledge to do this:
template<typename T>
void overloaded(T&& in)
{
if (std::is_lvalue_reference<T>::value)
std::cout << "lvalue" << std::endl;
else
std::cout << "rvalue" << std::endl;
}
In general, it is an anti-pattern to overload T&& templates with anything else. These special templates come in handy when you want to catch everything.

How does template argument deduction distinguish between an lvalue and a literal/compile-time value

This is a question related to OP's solution to Is constexpr useful for overload.
Basically, he used
template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, int>::type
f(T&& n) { ... }
and
template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value, int>::type
f(T&& n) { ... }
to know whether f() has been called with is a compile-time variable (e.g. literal: f(42)) or an lvalue (e.g. local variable: f(argc)) as its argument.
Q: How does that work ? (I expected, in both calls, that the first overload would be called (i.e. std::is_arithmetic<T>::value == true)
Here is a full example:
Run It Online
#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;
template<class T>
constexpr
typename std::enable_if<std::is_arithmetic<T>::value,
int>::type
inline f(T&& n)
{
//cout << "compile time" << endl;
return 1;
}
template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value,
int>::type
inline f(T&& n)
{
//cout << "run time" << endl;
return 0;
}
int main(int argc, char* argv[])
{
const int rt = f(argc);
constexpr int ct = f(42);
cout << "rt: " << rt << endl;
cout << "ct: " << ct << endl;
}
A template function of the form
template <typename T>
void func(T&& t);
looks as if it takes an r-value reference. But in actual fact T&& here is what Scott Meyers calls a universal reference, otherwise known as a forwarding reference. Different things can happen depending on the value category of the argument. Let's have a look at each case:
t is a non-const lvalue, for example
int i = 0;
func(i);
In this case, T is deduced to be an lvalue reference to int, that is, T=int&.
t is a const lvalue, for example
const int i = 1;
func(i);
Similarly, in this case T is deduced to be const int&.
t is an rvalue, for example
func(1);
In this case, T is deduced to be int just as we might have expected
Exactly why these deductions happen this way is to do with the rules for reference collapsing; I highly recommend reading Scott Meyers' article on the subject if you're interested.
The last case above also illustrates the point that in C and C++, literals (except string literals) are always rvalues.
What does this have to do with the enable_if? Well if your f is called with an integer literal, then T is deduced to be plain int. Obviously, is_arithmetic<int> is true, so the second function gets SFINAE'd out and the first is called.
However, when called with an lvalue, T is deduced to be (const) int&. A reference is not arithmetic, so the first function disappears leaving only the second to be called.

Preventing non-const lvalues from resolving to rvalue reference instead of const lvalue reference

I'm having trouble overloading a function to take a value either by const reference or, if it is an rvalue, an rvalue reference. The problem is that my non-const lvalues are binding to the rvalue version of the function. I'm doing this in VC2010.
#include <iostream>
#include <vector>
using namespace std;
template <class T>
void foo(const T& t)
{cout << "void foo(const T&)" << endl;}
template <class T>
void foo(T&& t)
{cout << "void foo(T&&)" << endl;}
int main()
{
vector<int> x;
foo(x); // void foo(T&&) ?????
foo(vector<int>()); // void foo(T&&)
}
The priority seems to be to deduce foo(x) as
foo< vector<int> & >(vector<int>& && t)
instead of
foo< vector<int> >(const vector<int>& t)
I tried replacing the rvalue-reference version with
void foo(typename remove_reference<T>::type&& t)
but this only had the effect of causing everything to resolve to the const-lvalue reference version.
How do I prevent this behaviour? And why is this the default anyway - it seems so dangerous given that rvalue-references are allowed to be modified, this leaves me with an unexpectedly modified local variable.
EDIT: Just added non-template versions of the functions, and they work as expected. Making the function a template changes the overload resolution rules? That is .. really frustrating!
void bar(const vector<int>& t)
{cout << "void bar(const vector<int>&)" << endl;}
void bar(vector<int>&& t)
{cout << "void bar(vector<int>&&)" << endl;}
bar(x); // void bar(const vector<int>&)
bar(vector<int>()); // void bar(vector<int>&&)
When you have a templated function like this you almost never want to overload. The T&& parameter is a catch anything parameter. And you can use it to get any behavior you want out of one overload.
#include <iostream>
#include <vector>
using namespace std;
template <class T>
void display()
{
typedef typename remove_reference<T>::type Tr;
typedef typename remove_cv<Tr>::type Trcv;
if (is_const<Tr>::value)
cout << "const ";
if (is_volatile<Tr>::value)
cout << "volatile ";
std::cout << typeid(Trcv).name();
if (is_lvalue_reference<T>::value)
std::cout << '&';
else if (is_rvalue_reference<T>::value)
std::cout << "&&";
std::cout << '\n';
}
template <class T>
void foo(T&& t)
{
display<T>();
}
int main()
{
vector<int> x;
vector<int> const cx;
foo(x); // vector<int>&
foo(vector<int>()); // vector<int>
foo(cx); // const vector<int>&
}
In order for T&& to bind to an lvalue reference, T must itself be an lvalue reference type. You can prohibit the template from being instantiated with a reference type T:
template <typename T>
typename std::enable_if<!std::is_reference<T>::value>::type foo(T&& t)
{
cout << "void foo(T&&)" << endl;
}
enable_if is found in <utility>; is_reference is found in <type_traits>.
The reason that the overload taking T&& is preferred over the overload taking a T const& is that T&& is an exact match (with T = vector<int>&) but T const& requires a qualification conversion (const-qualification must be added).
This only happens with templates. If you have a nontemplate function that takes a std::vector<int>&&, you will only be able to call that function with an rvalue argument. When you have a template that takes a T&&, you should not think of it as "an rvalue reference parameter;" it is a "universal reference parameter" (Scott Meyers used similar language, I believe). It can accept anything.
Allowing a T&& parameter of a function template to bind to any category of argument is what enables perfect forwarding.