6

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?

Loqz
  • 373
  • 4
  • 16
  • 7
    How could the compiler possibly deduce that you intend for the parameter to be a reference? And how would you write a template that takes *non*-reference parameters? (Tutorials are notorious for being wrong, by the way. Invest in a book.) – molbdnilo Dec 28 '15 at 14:49
  • 1
    @molbdnilo Recommending C++ books is best accompanied by [this link](http://stackoverflow.com/q/388242/1782465). – Angew is no longer proud of SO Dec 28 '15 at 14:50
  • @molbdnilo thanks for the info, really appreciate it. But I tell you, the tutorial the I read is highly rated, given that it's the best article of October 2011 for C++. This is the exact quote from it: "template parameter type T may be inferred from T&, T* or T[]". Here's the link of the reliable source http://www.codeproject.com/Articles/257589/An-Idiots-Guide-to-Cplusplus-Templates-Part#PtrWithTempl – Loqz Dec 28 '15 at 14:57
  • @Loqz: That statement does not mean what you think it means. – Lightness Races in Orbit Dec 28 '15 at 15:24
  • 3
    @Loqz Note that it says that the template parameter `T` can be inferred *from* function parameters of the types `T&`, `T*, and `T[]`, not that it can be inferred *to be* those types. (Having read that article, I stand by my assessment of tutorials. It's a horrible text, full of vague and confusing descriptions of half-grasped ideas.) – molbdnilo Dec 28 '15 at 15:25
  • @molbdnilo I see, thanks! So is it overall advisable to just use reference -type most of the time when dealing with templates? Cuz aside from above, it also helps in the compilation process as just T tend to have larger size than T&. – Loqz Dec 28 '15 at 15:37

4 Answers4

13

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.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • wow, great answer! All seems clearer now. I must say I'm new to learning C++ by myself, so there are a bit new unfamiliar terms to me in your discussion, specifically, what does the double ampersand '&&' mean right after 'T'? I thought thats only used for the logical AND. Lastly, in the summary part, what do you mean by _temporary_? – Loqz Dec 28 '15 at 23:40
  • @Loqz confusingly, && also means 'rvalue reference' when used in a declaration. It's the kind of reference you get when you create a temporary argument to a function call, and it's the return type of std::move. I can't give you a full lesson here but make sure the books you are using cover c++11. They will cover the concepts. – Richard Hodges Dec 29 '15 at 09:49
12

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.

6

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.

Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
  • 2
    While this answer is certainly true, it should be noted that even if the type of `a` was actually `int &`, `T` would still be deduced to an `int`. Unfortunately, I don't think there is a correct and succint way of stating the C++ type deduction rules. – Angew is no longer proud of SO Dec 28 '15 at 14:49
  • The "*declared* type" of `a` is irrelavent here. When `f(expr)` is used to deduce a type `T` related to `f`, only the type of the *expression* `expr` (which can never be a reference type) and the value category of it matter. In other words, no matter how `f` is declared, `f(a)` always give the same deduction for `a` declared as `int a`, `int& a`, and `int&& a` because in all these cases `a` is a lvalue expression of int type. – Weijun Zhou Dec 29 '22 at 11:28
0

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;
 }
micebrain
  • 568
  • 2
  • 9