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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With