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

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
Šimon Tóth
  • 35,456
  • 20
  • 106
  • 151

2 Answers2

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&.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 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? :-/ – Šimon Tóth Dec 01 '10 at 18:45
  • @Let: Well, you are forwarding correctly, so what's the problem? – fredoverflow Dec 01 '10 at 21:51
  • @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. – Šimon Tóth Dec 01 '10 at 21:57
  • @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. – fredoverflow Dec 01 '10 at 21:59
  • @Fred Your post seems to solve this, but I have been talking about writing correct code for both r-value and l-value references. – Šimon Tóth Dec 01 '10 at 22:01
  • @Let: You really want `f` to always mutate `t`, even if `t` is bound to an rvalue? Then simply change `f(forward

    (t))` to `f(t)`. This works because `t` is a name and hence an lvalue. Yes, named rvalue references are lvalues. Confusing at first, I know :) But maybe you should explain in more detail what you really want to *achieve*. "This code won't work" and "writing correct code" does not tell me anything about your actual *goal*.

    – fredoverflow Dec 01 '10 at 22:02
  • @Fred Uh, why does that work? `g(f,10);` would call `f(10);` but that shouldn't work. – Šimon Tóth Dec 01 '10 at 22:11
  • @Fred I'm just heavily confused about the correct way of writing template functions/methods taking r-value references. – Šimon Tóth Dec 01 '10 at 22:13
  • 1
    @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? – fredoverflow Dec 01 '10 at 22:24
  • @Let: I'm still not clear on one point: Do you want to write a function template that a) accepts **only rvalues** (then see my solution with `enable_if`) or b) accepts **both lvalues and rvalues** (then your `forward` should do the trick)? ... You seem to think that `T&&` somehow means "rvalue reference"? That is not the case due to reference collapsing rules (see Johannes' answer). – fredoverflow Dec 01 '10 at 22:29
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);
}
fredoverflow
  • 256,549
  • 94
  • 388
  • 662