11

Can anyone please explain why this compiles and why does t end up with type int&?

#include <utility> void f(int& r) { ++r; } template <typename Fun, typename T> void g(Fun fun, T&& t) { fun(std::forward<T>(t)); } int main() { int i = 0; g(f, i); } 

I see this on GCC 4.5.0 20100604 and GDB 7.2-60.2

0

2 Answers 2

17

Because of perfect forwarding, when the argument to P&& is an lvalue, then P will be deduced to the argument's type plus having a & attached. So you get int & && with P being int&. If the argument is an rvalue then P will be deduced to only the argument's type, so you would get an int&& argument with P being int if you would pass, for example 0 directly.

int& && will collapse to int& (this is a semantic view - syntactically int& && is illegal. But saying U && when U is a template parameter or a typedef refering to a type int&, then U&& is still the type int& - i.e the two references "collapse" to one lvalue reference). That's why t has type int&.

Sign up to request clarification or add additional context in comments.

10 Comments

Oh crap, this complicates using r-value references a ton. So I need to write templated code so it will work for both r-value and l-value references? :-/
@Let: Well, you are forwarding correctly, so what's the problem?
@Fred The problem is that this code won't work for r-value references (and temporaries), even though the interface provides an r-value reference.
@Let: It does not work because f only accepts lvalues. If you want to limit g to accepting lvalues only, see my own answer below.
@Let: No, g(f, 10) does not call f(10), that would be impossible since int& does not bind to rvalues such as 10. Since 10 is an rvalue, T&& is deduced to be int&&, and hence t is bound to a temporary object that is initialized to 10. t itself, like all other names, is an lvalue, and as such it can be bound to int& r. The modification ++r can be observed in g, simply print t to the console and you will see it. A name is always an lvalue, even if is the name of an rvalue reference. Does that clear things up?
|
3

If for some reason you really want to bind to lvalues or rvalues specifically, use metaprogramming:

#include <type_traits> template <typename T> typename std::enable_if<std::is_lvalue_reference<T&&>::value, void>::type fun(T&& x) { std::cout << "lvalue argument\n"; } template <typename T> typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type fun(T&& x) { std::cout << "rvalue argument\n"; } int main() { int i = 42; fun(i); fun(42); } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.