简体   繁体   中英

C++ Templates can't deduce Reference Types without using '&'

Here's a very simple example:

#include <iostream>

template<typename T>
void DoubleMe(T x) {
    x += x;
}

int main()
{
    int a = 10;
    DoubleMe(a);
    std::cout << a; //displays 10 not 20!
}

In cases like this, am I forced to use 'T&' in the function argument instead? Cuz I have read in some tutorials that templates can correctly deduce the appropriate data type, including T*, T[ ], or T& by just defining a simple 'T' before the variable. Help?

Yes, to get the effect you want here, you must add the ampersand.

As you write, templates can correctly deduce data types. However, they cann deduce intent. In this case, the type you pass it is an integer, and it correctly instantiates an integer function that internally doubles the argument passed to it by value. The fact that you meant the function to have a side effect, is not something the compiler can guess.

You can indeed correctly deduce reference types with plain T . Unfortunately, that does not mean what you think it means.

Given

template <typename T> struct S { };
template <typename T> void f(S<T>) { }
int main() { f(S<int&>{}); }

the type argument for f is correctly deduced as int& .

The problem is that in your case, deducing T as int produces a perfectly valid function already. Perhaps a slight oversimplification, but type deduction produces the simplest T that makes the call work. In your case, T = int makes the call work. Not the way you want it to work, but the compiler can't know that. T = int & would also make the call work, but it's not the simplest T that makes it work.

Maybe this will help you to see it the way the compiler does (apologies to any language lawyers if I am oversimplifying):

In this example, the compiler must infer the type of T to be the type that makes the function declaration legal with the least amount of deduction. Since a T can usually be copy-constructed directly from a const T& (which implies that it can also be copy-constructed from a T&), your function will take a copy.

template<class T>
void foo(T value); 

In this example T must be the type of the object thing ref refers to - and since references cannot refer to references, T must be a (possibly const, possibly volatile) type.

template<class T>
void foo(T& ref);

In this example, ref must be referring to a const (possibly volatile) object of type T:

template<class T>
void foo(const T& ref);

In this example, ref may either be a reference or an r-value reference. It's known as a universal reference. You're actually writing two functions in one and it's often the most efficient way to handle the case where you are taking ownership of ref .

template<class T>
void foo(T&& ref);

// called like this:
foo(Bar());   // means ref will be a Bar&&

// called like this:
Bar b;
foo(b);       // means ref will be a Bar&

// called like this:
const Bar b;
foo(b);       // means ref will be a const Bar&

In summary:

void foo(T value) means I will make a copy of whatever you give me.

void foo(T& value) means I wont make a copy but I may modify your value. You may not give me a temporary .

void foo(const T& value) means I wont make a copy and I cannot modify your copy. You may give me a temporary.

void foo(const T&& value) means I may steal or modify the contents of your value, even if it's a temporary .

If you want the function to pass the argument by value, you need to return the value with the same argument type.

T DoubleMe(T x) {
     return x + x;
 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM