4

I took sample from http://www.cplusplus.com/reference/utility/forward:

// forward example
#include <utility>      // std::forward
#include <iostream>     // std::cout

// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}

// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
    overloaded (x);                   // always an lvalue
    overloaded (std::forward<T>(x));  // rvalue if argument is rvalue
}

int main () {
    int a;

    std::cout << "calling fn with lvalue: ";
    fn (a);
    std::cout << '\n';

    std::cout << "calling fn with rvalue: ";
    fn (0);
    std::cout << '\n';

    return 0;
}

which prints

calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]

But if I want to make overloaded template, like that:

template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}
template<typename T>
void overloaded (T&& x) {std::cout << "[rvalue]";}

it prints

calling fn with lvalue: [rvalue][rvalue]
calling fn with rvalue: [rvalue][rvalue]

Is there any way to use template function to deduct what an object I've transfered to function?

J. S.
  • 425
  • 4
  • 13
  • 1
    Your `T&&` is a universal reference. (https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers) What you're looking for is perfect forwarding of a universal reference. – Jazzwave06 Apr 29 '18 at 12:57
  • @sturcotte06 It's called a *forwarding* reference :) – Rakete1111 Apr 29 '18 at 13:01
  • 2
    The accepted answer is really not what you want... const is a hack that makes thsi specific example work but it's not he right thing generally. What you need to do is constrain the templates with enable_if. – Nir Friedman Apr 29 '18 at 13:10

3 Answers3

6

Yes it's possible, just add a const to your second overload:

template<typename T>
void overloaded (const T& x);
template<typename T>
void overloaded (const T&& x);
//               ^^^^^

The reason why you need to const is to make x not a forwarding reference. Forwarding references are very greedy, and if you don't pass in the exact same type (including any cv qualifiers) then the forwarding reference overload will get chosen.

In your case, because you do not pass a const object to overload, the second overload will always be a better match.

But if you add a const there, then it's not a forwarding reference anymore and can only accept rvalues and no lvalues, and won't be a better match for an lvalue as a result but will be a better match for any rvalues than the const& overload.


If you need to move from x, then you will have to do something else. Remove the const& overload and branch in the forwarding reference whether you have an rvalue or lvalue:

template <typename T> void overloaded(T &&x) {
  if /*constexpr*/ (std::is_lvalue_reference_v<T>)
    std::cout << "[lvalue]";
  else
    std::cout << "[rvalue]";
}

Note: You'll need to use if constexpr if you do specific stuff that is not valid for a branch or the other.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • 4
    Not too helpful if the rvalue overload wants to move from the argument. – aschepler Apr 29 '18 at 13:03
  • 2
    Yeah but you have now created a new problem, the argument is const inside the function body which is rated what you want. This isn't really how you solve this problem. – Nir Friedman Apr 29 '18 at 13:06
  • 1
    Fair enough, but I don't think OP needs to move anyways given that this is example code. – Rakete1111 Apr 29 '18 at 13:12
5

Whenever a function template parameter has type "rvalue reference to a type template parameter", like your x in template<typename T> void overloaded(T&& x);, it becomes a "forwarding reference" aka "universal reference". These follow the special rule that they can match either an lvalue or rvalue argument.

When the argument is an rvalue, T has its type as a non-reference, and the parameter type is an rvalue reference.

When the argument is an lvalue, T is an lvalue reference type. This makes sense because of a second "reference-collapsing rule": if you add a & or && to a type alias (a typedef, type defined with using, or type template parameter) which is already a reference, it forms a reference type which is an rvalue reference if both the alias is an rvalue reference and you add &&, or an lvalue reference in the other three cases.

So when you call overloaded(x) when x is an lvalue of type int, both overloads of overloaded match:

template<typename T>
void overloaded (const T& x);
// Specialization void overloaded<int>(const int&);

template<typename T>
void overloaded (T&& x);
// Specialization void overloaded<int&>(int&);

But the second specialization is a better match for the argument type, so it wins.

To prevent this, I would use SFINAE techniques to restrict the second overloaded to only non-reference types.

#include <type_traits>

// As before:
template<typename T>
void overloaded (const T& x);

template<typename T>
auto overloaded (T&& x) -> std::enable_if_t<!std::is_reference<T>::value>;

Now given any lvalue argument, the second template will deduce T to be an lvalue reference type, fail to substitute that into the return type, and the template will then be ignored for overload resolution, so that the first overload can be used instead.

aschepler
  • 70,891
  • 9
  • 107
  • 161
2

The second overloaded template isn't for rvalues, but rather it's a forwarding reference. It tends to be very aggressive about picking up call because it will deduce both const-enss and value-ness exactly correctly making it preferred in almost all cases (your int call is with an lvalue but it's not const, so that's why it gets called).

To make this work, you need to constrain that template. When that template matches an lvalue, T will be of reference type so that reference collapsing rules say that T&& will collapse to T&. So we can do this:

template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}

template<typename T, std::enable_if_t<!std::is_reference<T>::value, int> = 0>
void overloaded (T&& x) {std::cout << "[rvalue]";}

If you do this then you should get the expected output.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72