51 / 163 · C++11 · 10 min
Template deduction: match parameters first, then instantiate code
A template is a family of declarations and implementations generated from parameters, not a way to force every argument into one type. Understanding deduction by value versus by reference, non-type parameters, and definition visibility makes it faster to explain failed calls, array decay, and dependent-name errors.
In this lesson
Deduction is not ordinary argument conversion
A function-template call first matches template parameters from formals and actuals, then forms a candidate that can take part in overload resolution. For template<class T> T larger(T, T), passing int and double gives T conflicting information; deduction usually cannot first convert as an ordinary double parameter would. You can specify T explicitly, or design the interface with two type parameters.
A by-value parameter ignores top-level const on the argument and lets arrays and functions decay; a by-reference parameter can keep array bounds and const. The example’s array-reference parameter const T (&)[N] deduces both element type and length. N is a compile-time numeric parameter; there is no need to pass a separate run-time length that might be wrong.
What the point of instantiation needs to see
Template definitions usually live in headers so that a using translation unit can instantiate them when needed; the language does not require every template to be written in a .h. A closed set of types can also be compiled separately via explicit instantiation, but that limits the supported types and needs a maintained instantiation list.
Names that depend on template parameters cannot always be settled on the first parse. For example, typename C::value_type tells the compiler it is a type; a member template on a dependent object sometimes also needs template to disambiguate. These are syntactic facts; they add no run-time cost and are not there so the compiler will “try harder to guess.”
Let the interface expose real constraints
The example’s larger returns the chosen object by value, avoiding handing the caller a reference to a temporary argument; the array-length function only reads type information and never touches the elements. Type asserts and value asserts check deduction results and business results separately; those two kinds of problem should not be mixed.
That a template can instantiate does not mean every input matches the business meaning. A comparison still needs a sensible ordering, and arithmetic can still overflow. C++20 concepts can write some type conditions on the interface early, but they cannot prove every run-time property. When a diagnostic is long, look first for the earliest violated parameter requirement, rather than starting from the last layer of standard-library errors.
Pitfalls
- The same T deduced from several parameters must be consistent; do not expect the deduction stage to search automatically for a common type everything can convert to.
- Returning const T& can skip one copy, but it may hand the caller a reference to a temporary argument; a generic return strategy must make lifetime explicit.
Run an example
Minimum C++11 · complete program · Download .cpp
#include <cassert>
#include <cstddef>
#include <iostream>
#include <type_traits>
template<class T>
T larger(T a, T b) { return a < b ? b : a; }
template<class T, std::size_t N>
constexpr std::size_t extent(const T (&)[N]) { return N; }
int main() {
const int values[] = {2, 4, 6};
static_assert(extent(values) == 3, "array extent survives");
auto result = larger<double>(2, 3.5);
static_assert(std::is_same<decltype(result), double>::value, "explicit type");
assert(result == 3.5);
assert(larger(2, 4) == 4);
std::cout << extent(values) << ' ' << larger(2, 4) << '\n';
}
Compile locally
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread modern-templates.cpp -o example && ./exampleExpected result
3 4
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
If extent’s parameter is changed to const T*, can N still be deduced from an array call? Why does the original version not accept an ordinary int*?
Show a reference answer
No. After an array decays to a pointer, the pointer type has no element count; if the template parameter N is kept, it cannot be deduced. The original version requires a true array reference; an ordinary int* has no matching array bound. A run-time contiguous region should use a pointer plus a length, or span in C++20.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.