- During template type deduction, arguments that are references are treated as non-references, i.e., their reference-ness is ignored.
- When deducing types for universal reference parameters, lvalues arguments get special treatment.
- When deducing types for by-value parameters, const and/or volatile arguments are treated as non-const and non-volatile.
- During template type deduction, arguments that are array or function names decay to pointers, unless they're used to initialize references.
Template type deduction is great thing to use: programmers just plug in values and the template works perfectly. Many even don't know the exact type the template is initiated with.
But with the introduction of C++11/14, auto and decltype make thing a bit harder.
We will base our discussion in the following based on such a template:
template<typename T>
void f(ParamType param);
f(expr); // call f with some expression
The rules are:
- If expr's type is reference, ignore the reference part.
- Then pattern-match expr's type against ParamType to determine T.
For example,
template<typename T>
void f(T& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T is int, param's type is int&
f(cx); // T is const int, param's type is const int&
f(rx); // T is const int, param's type is const int&
If we change the type of f's parameter from T& to const T&, things change a little because const no longer needs to be deduced as part of T:
template<typename T>
void f(const T& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T is int, param's type is const int&
f(cx); // T is int, param's type is const int&
f(rx); // T is int, param's type is const int&
If param were a pointer (or a pointer to const) instead of a reference, things work essentially the same:
template<typename T>
void f(T* param);
int x = 27;
const int *px = &x;
f(&x); // T is int, param's type is int*
f(px); // T is const int, param's type is const int*
To know about what is "Universal Reference", see Item 24.
The rules are:
- If expr is an lvalue, both T and ParamType are deduced to be lvalue reference.
- If expr is an rvalue, the "normal" (case1) rule applies.
For example:
template<typename T>
void f(T&& param); // param is now a universal reference
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // x is lvalue, so T is int&, param's type is also int&
f(cx); // cx is lvalue, so T is const int&, param's type is also const int&
f(rx); // rx is lvalue, so T is const int&, param's type is also const int&
f(27); // 27 is rvalue, so T is int, param's type is int&&
In this case, we're dealing with pass-by-value. The rules are:
- As before, if expr's type is a reference, ignore the reference part.
- If, after ignoring expr's reference-ness, ignore any const and volatile as well.
The example goes as follows:
template<typename T>
void f(T param); // param is now pass-by-value
int x = 27;
const int cx = x;
const int& rx = x;
const char* const ptr = "Fun with pointers";
f(x); // T's and param's type are int
f(cx); // T's and param's type are int
f(rx); // T's and param's type are int
f(ptr); // T's and param's type are const char*
There are some rare cases, just good to know.
When we pass an array's name to a function as parameters, it actually decays into the pointer pointing to the first element in the array. Therefore,
template<typename T>
void f(T param); // pass-by-value
const char name[] = "J. P. Briggs"; // name's type is const char[13]
f(name); // T is deduced as const char*
But it can really deduce out the array type if we add reference to arrays!
template<typename T>
void f(T& param);
f(name); // pass array to f
Besides, function also decays into function pointers.
void someFunc(int, double); // type is void(int, double)
template<typename T>
void f1(T param);
template<typename T>
void f2(T& param);
f1(someFunc); // param deduced as ptr-to-func; type is void (*)(int, double)
f2(someFunc); // param deduced as ref-to-func; type is void (&)(int, double)