In C++11, the standards committee added the ability to automatically deduce a template function's type information based on the arguments that were passed to the function.
Check out this example:
template<typename T>
void foo(T t)
{
show_type(t);
}
The preceding function creates a standard template function that executes a function called show_type() designed to output the type information that it is provided with.
Before C++11, we would use this function as follows:
int main(void)
{
int i = 42;
foo<int>(i);
foo<int>(42);
}
The compiler already knows that the template should define the T type as an integer as that is what the function was provided for. C++11 removes this redundancy, allowing the following:
int main(void)
{
int i = 42;
foo(i);
foo(42);
}
This results in the following output when executed:
Like auto, however, this type deduction gets interesting when r-value references are used, as follows:
template<typename T>
void foo(T &&t)
{
show_type(t);
}
The preceding example defines t as a forwarding reference (also known as a universal reference). The universal reference takes on whatever reference type it is passed. For example, we call this function as follows:
int main(void)
{
int i = 42;
foo(i);
}
We get the following output:
The preceding output shows that the template function was given an l-value reference to an integer. This is because i, in our main function, is an l-value, even though the function appears to be requesting an r-value reference. To get an r-value reference, we must provide an r-value, as follows:
int main(void)
{
int i = 42;
foo(std::move(i));
}
This results in the following output when executed:
As shown in the preceding screenshot, now that we have given the universal reference an r-value, we get an r-value. It should be noted that a universal reference only has the following signature:
template<typename T>
void foo(T &&t)
For example, the following is not a universal reference:
template<typename T>
void foo(const T &&t)
Neither is the following a universal reference:
void foo(int &&t)
Both of the preceding examples are r-value references, and hence require that an r-value be provided (in other words, both of these functions define move operations). A universal reference will accept both an l-value and an r-value reference. Although this seems like an advantage, it has the downside that it is sometimes difficult to know whether your template function has received an l-value or an r-value. Currently, the best way to ensure your template function acts like an r-value reference and not a universal reference is to use SFINAE:
std::is_rvalue_reference_v<decltype(t)>
Finally, it is also possible to perform type deduction on less common types such as C-style arrays, as in this example:
template<typename T, size_t N>
void foo(T (&&t)[N])
{
show_type(t);
}
The preceding function states that we wish to have a C-style array of type T and size N passed to the function and then outputs its type when executed. We can use this function as follows:
int main(void)
{
foo({4, 8, 15, 16, 23, 42});
}
This automatically deduces to an r-value reference of a C-style array of type int and size 6. As shown in this recipe, C++ provides several mechanisms for allowing the compiler to determine what types are leveraged in template functions.