I'm wondering why, in the following example, the function func has this signature:
void func<int&>(int&)
I know that named rvalue references are treated like lvalue references. I'm wondering why they aren't const lvalue references? I was expecting to see void func<const int&>(const int&) as a signature of func function.
template<typename T>
void func(T&& t) {}
int main()
{
int&& t = 5;
func(t);
}
Related
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.
#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&.
What is the correct way to accept a parameter pack for perfect-forwarding, such that it will take any type and simply forward them? The following code works on regular types but fails on pointer types:
template<typename ...A>
void b(A &&... args)
{}
template<typename ...A>
void a(A &&... args)
{
b(std::forward<A>(args)...);
}
int main() {
// ok
a<int>(5);
// error: cannot bind rvalue reference of type ‘int*&&’ to lvalue of type ‘int*’
int *foo = nullptr;
a<int*>(foo);
return 0;
}
[edit] Thanks for the quick and great replies! I oversimplified - this is a closer example of the problem I am trying to solve:
#include <iostream>
using namespace std;
template<typename F>
struct fun;
template<typename F, typename ...A>
struct fun<F(A...)>
{
void b(A &&... args)
{}
void a(A &&... args)
{
b(std::forward<A>(args)...);
}
};
int main() {
// ok
fun<void(int)> f1;
f1.a(5);
// error: cannot bind 'int' lvalue to 'int&&'
fun<void(int)> f2;
int x = 5;
f2.a(x);
return 0;
}
In this case, I don't have the luxury to let the template adjust automatically... Any idea how this can be achieved?
[edit 2] as pointed out in the comments, this has nothing to do with pointers, I updated my sample to simply use an lvalue
You should not specify template arguments explicitly; which just prevents template argument deduction from working with forwarding reference, and yields unexpected result.
a<int>(5); // A is specified as int then function parameter's type is int&&.
// 5 is an rvalue and could be bound to int&&
a<int*>(foo); // A is specified as int* then function parameter's type is int* &&.
// foo is an lvalue and couldn't be bound to int* &&
Just
a(5); // 5 is rvalue, then A is deduced as int and function parameter's type collapses to int&&
int *foo = nullptr;
a(foo); // foo is lvalue, then A is deduced as int* & and function parameter's type collapses to int* &
EDIT
Firstly both the member functions b and a are not template, and their parameters are not declared as forwarding reference at all.
The code doesn't work because
fun<void(int)> f2;
int x = 5;
f2.a(x); // A is specified as int then function parameter's type is int &&.
// x is an lvalue and couldn't be bound to int &&
I'm not sure about your intent, you can change it to
fun<void(int&)> f2;
// ^
int x = 5;
f2.a(x); // A is specified as int& then function parameter's type collapses to int&.
// x is an lvalue and could be bound to int&
Or make them function templates and still apply forwarding reference.
template <typename... T>
void b(T &&... args)
{}
template <typename... T>
void a(T &&... args)
{
b(std::forward<T>(args)...);
}
This question already has an answer here:
Why does a templated rvalue reference accept lvalues?
(1 answer)
Closed 4 years ago.
This program:
using namespace std;
#include <iostream>
#include <memory>
struct Dog
{
int legs;
} adog;
Dog gimmeadog() { return adog; }
void walk(Dog && d) { cout << "Nonconst right dog walk\n"; }
//template<class T> void walk(T && d) { d.legs=3; cout << "Nonconst right something walk\n"; }
int main() {
Dog mydog = gimmeadog();
walk(mydog);
return 0;
}
correctly fails to compile on gcc because:
error: cannot bind rvalue reference of type ‘Dog&&’ to lvalue of type ‘Dog’
walk(mydog);
but if you un-comment the template it happily binds and prints "Nonconst right something walk".
Why? What type is T taking when it works? Is this not defeating the object of rvalue references?
When T is a template argument T&& is a forwarding reference, not a r-value one. T gets deduced as T for r-values and T& for l-values. Such a reference binds to anything.
template<class T>
void walk(T && d)
in this case T&& is forwarding reference, when you pass Lvalue (mydog object) T is deduced to be T&, so signature of walk looks like
void walk(Dog& );
then you can bind mydog to Lvalue 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.