Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I make template type deduction work with references?

I have a template function, func:

template<typename T>
void func(T p)  { f(p); }

And a set of functions f:

f(SomeType&);
f(int);
...

If I instantiate the template function, func, using a reference as function argument p, without explicitly specifying template parameter T, then the type deduced will not be the reference type of p, but rather the type p is a reference to, for example:

SomeType s;
SomeType& ref_to_s = s;
func(ref_to_s);             // Type deduction results in: func<SomeType>(ref_to_s)
func<SomeType&>(ref_to_s);  // Need to explicitly specify type to work with reference

So, my questions are:

  • Why does the compiler fail to deduce the reference type SomeType& in the above situation?
  • Is there a way to define template function func, so that type deduction works with a reference type, without explicit specification of the template parameter T?

To be clear, I'd like something working with both (see functions f above):

func(ref_to_s);    // Calls func<SomeType&>(ref_to_s)
func(1);           // Calls func<int>(1)
like image 469
shrike Avatar asked May 06 '16 09:05

shrike


1 Answers

The answer to the first part is that initializing an object from a value and binding a reference to a value are considered equally good (both are "exact matches" in overload resolution), and so the referenciness of a parameter is not deduced. Rather, you must specify it exactly. For a related example consider a hypothetical construction

T x = f();

where T is (for the duration of this thought experiment) something you're supposed to deduce. What should x be - an object, or a reference? What would it depend on? The expression on the right has a value, of course, and the type of that value is always an object type. How should you decide? You could query the value category of the expression, but that's too subtle. So instead, you have to say what you want:

T x = f();    // x is an object
T & r = f();  // r is a reference

In C++, this mechanism is actually available if you replace T with auto. The same rules apply to template argument deduction for function templates.

Now on to how to solve your problem. The usual idiom is to make the function template always take a reference, and then forward the argument to the inner call:

template <typename T>
void func(T && t)
{
    f(std::forward<T>(t));
};

Now func's parameter is always a reference in any specialization (but thanks to a weird rule called "reference collapsing" it can be either an lvalue or an rvalue reference), and that value is forwarded with the same value category group to f, and this overload resolution on f allows lvalue-reference overloads to be selected when the original argument for func was an lvalue.

like image 176
Kerrek SB Avatar answered Sep 30 '22 19:09

Kerrek SB