7

I am trying to understand how lvalues bind to rvalue references. Consider this code:

#include <iostream>

template<typename T>
void f(T&& x) {
    std::cout << x;
}

void g(int&& x) {
    std::cout << x;
}

int main() {
    int x = 4;
    f(x);
    g(x);
    return 0;
}

While the call to f() is fine, the call to g() gives a compile-time error. Does this kind of binding work only for templates? Why? Can we somehow do it without templates?

r.v
  • 4,697
  • 6
  • 35
  • 57

2 Answers2

7

Since T is a template argument, T&& becomes a forwarding-reference. Due to reference collapsing rules, f(T& &&) becomes f(T&) for lvalues and f(T &&) becomes f(T&&) for rvalues.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
David G
  • 94,763
  • 41
  • 167
  • 253
  • That makes sense. But suppose I don't need templates, then is there any way to get universal references, say in the example above? I edited the question slightly for this. – r.v Jun 15 '13 at 18:16
  • @r.v You can provide a second overload of `g` for lvalues (`void g(int&)`), or you can *move* the lvalues with `std::move`. – David G Jun 15 '13 at 18:19
  • Yes, these are possible solutions but I was hoping we get something in line of a universal reference: given a type T (fixed, not templated), I can match both T& and T&& in one call (no overloads) and then use forwarding, etc. That definitely helps to reduce some redundant code. – r.v Jun 15 '13 at 18:23
  • @r.v Sorry, but excluding what I have suggested this is impossible to do without templates. :( Perfect forwarding works great with templates by the way. – David G Jun 15 '13 at 18:25
  • No problem, thanks. I think we have a legitimate use case here though. – r.v Jun 15 '13 at 18:27
1

0x499602D2 has already answered your question; nevertheless, the following changes to your code might give further insights.

I have added a static_assert to f to check the deduced type:

#include <type_traits>

template<typename T>
void f(T&& x) {
    static_assert(std::is_same<T&&, int&>::value,"");
    std::cout << x;
}

The assert does not fail, so the type of x in f is eventually int& (in this particular example).

I have changed how g is called in main:

g(std::move(x));

Now the code compiles and the program works as expected and prints 44.

Hope this helps a bit in understanding rvalue references.

Ali
  • 56,466
  • 29
  • 168
  • 265
  • 1
    There's already a template for that: [`std::is_rvalue_reference`](http://en.cppreference.com/w/cpp/types/is_rvalue_reference) – David G Aug 28 '13 at 17:40