C++ / TEMPLATES AND GENERIC PROGRAMMING
Function templates and deduction of arguments
Write function templates, predict exactly what the compiler deduces for each template parameter, and fix conflicting-deduction errors without guesswork.
What you will learn
- Call a function template without angle brackets and predict what T is deduced as
- Fix a conflicting-deduction error with an explicit argument or a second parameter
- Predict how by-value deduction drops const, references and array bounds
- Deduce an array's length as a non-type parameter with const T (&)[N]
Understanding Function templates and deduction of arguments
A function template is not a function. It is a pattern the compiler uses to write functions for you: every distinct set of template arguments produces a separate ordinary function, compiled with T replaced throughout. Template argument deduction is the step that lets you call that pattern with plain call syntax, because the compiler matches the type of each argument against the corresponding parameter pattern and solves for T, turning larger(3, 7) into a call to larger<int>.
Deduction is a matching process, not a conversion process. Each parameter is deduced independently, and if two arguments produce different answers for the same T the call is rejected outright; the compiler will not promote int to double to make them agree. For a by-value parameter T the argument type is first decayed: top-level const and volatile are discarded, references are stripped, and arrays and functions become pointers, because the parameter is a fresh copy and none of those properties survive a copy. Take the parameter as const T& and no decay happens, which is how a pattern like const T (&)[N] can capture an array's element type and its length at once.
Two things deduction never looks at are the return type and the context of the call. In double x = mid(1, 2) the compiler deduces only from 1 and 2, so a template returning T hands back an int, integer division included, and converts afterwards. A template parameter that appears only in the return type therefore has to be written out by the caller, and since explicit template arguments are filled in from the left, such parameters belong at the front of the template parameter list. Once a parameter is supplied explicitly, deduction stops for it and the ordinary implicit conversions apply to the arguments again, which is exactly why larger<double>(3, 7.5) compiles while larger(3, 7.5) does not.
<cstddef>
<iostream>
<string>
// T is deduced from both arguments, and both must agree.
template <typename T>
T larger(T a, T b) {
return (a < b) ? b : a;
}
// N is deduced from the array type; the caller never writes it.
template <typename T, std::size_t N>
std::size_t length(const T (&)[N]) {
return N;
}
int main() {
std::cout << larger(3, 7) << '\n'; // T = int
std::cout << larger(2.5, 1.5) << '\n'; // T = double
std::cout << larger<double>(3, 7.5) << '\n'; // T given, 3 converts to 3.0
std::cout << larger(std::string("pear"), std::string("apple")) << '\n';
int xs[] = {1, 2, 3, 4, 5};
std::cout << length(xs) << '\n'; // N = 5
const int c = 10;
std::cout << larger(c, 4) << '\n'; // T = int, the const is dropped
}
Deduction solves for the template parameters by matching argument types against the parameter patterns, and it never applies a conversion to make a match fit.
Worked examples
One T for two arguments, or two parameters
Shows the conflicting-deduction error, the explicit-argument fix, and what independent template parameters cost you.
<iostream>
template <typename T>
T mid(T a, T b) { return (a + b) / 2; }
template <typename A, typename B>
auto mid2(A a, B b) { return (a + b) / 2; }
int main() {
// mid(1, 2.5); // error: T deduced as int from 1 and double from 2.5
std::cout << mid<double>(1, 2.5) << '\n';
std::cout << mid2(1, 2.5) << '\n';
std::cout << mid2(1, 2) << '\n';
}
Example explained
Line 1The commented call fails because the first argument deduces T = int and the second T = double; deduction reports a conflict instead of picking the wider type.
Line 2mid<double>(1, 2.5) skips deduction for T, so the literal 1 is converted to 1.0 by the normal rules and the average is 1.75.
Line 3mid2 has two independent parameters, so nothing has to agree, and its deduced return type follows whatever a + b produces (C++14 or later).
Line 4mid2(1, 2) shows the price of that flexibility: both are int, so (1 + 2) / 2 is integer division and the answer is 1, not 1.5.
By-value deduction decays the argument type
Demonstrates that const, reference-ness and array-ness are gone by the time T is deduced for a by-value parameter.
<iostream>
<type_traits>
template <typename T>
void by_value(T) {
std::cout << std::boolalpha
<< "int: " << std::is_same_v<T, int>
<< " const int: " << std::is_same_v<T, const int>
<< " pointer: " << std::is_pointer_v<T> << '\n';
}
int main() {
const int c = 1;
const int& r = c;
int a[3] = {};
by_value(42);
by_value(c);
by_value(r);
by_value(a);
}
Example explained
Line 1by_value(c) deduces T = int rather than const int: top-level const is dropped because the parameter is a copy the function may modify freely.
Line 2by_value(r) also deduces int, since reference-ness of the argument is never part of a by-value deduction.
Line 3by_value(a) deduces int*, because an array argument decays to a pointer to its first element, so sizeof inside the template would say nothing about the array.
Line 4std::is_same_v and single-argument boolalpha printing require C++17; compile with -std=c++17.
Non-deducible parameters go first
Shows why a template parameter used only as the return type must be listed before the deducible ones.
<iostream>
template <typename To, typename From>
To convert(From v) { return static_cast<To>(v); }
template <typename From, typename To>
To convert_bad(From v) { return static_cast<To>(v); }
int main() {
std::cout << convert<int>(3.9) << '\n';
std::cout << convert<char>(66) << '\n';
std::cout << convert_bad<double, int>(2.7) << '\n';
}
Example explained
Line 1convert<int>(3.9) supplies To by hand while From is still deduced as double from the argument, so you only write what the compiler cannot work out.
Line 2Explicit template arguments are consumed left to right, which is the whole reason To must be declared before From.
Line 3convert<char>(66) returns a char, and operator<< prints it as the character B instead of the number 66.
Line 4convert_bad lists the deducible parameter first, so the caller must spell out both arguments and the deduction of From gains nothing.
Important notes
Deduction ignores the return type and the variable being assigned to, so double avg = mid(1, 2); still instantiates the int version and averages with integer division.
A parameter written T&& in a function template is not a plain rvalue reference; it deduces by separate rules covered in the perfect-forwarding lesson.
Common mistakes
Reading "deduced conflicting types for parameter 'T'" from larger(3, 7.5) as a compiler bug: deduction will not convert 3 to 3.0, so the call itself must change via an explicit argument or matching literals.
Passing an array to template <typename T> void f(T arr) and using sizeof(arr) as the element count; the parameter is really a pointer, so the count is wrong and the loop runs off the end of the array.
Defining a function template in a .cpp file and calling it from another translation unit: that unit has no definition to instantiate, so you get an undefined-reference link error rather than a compile error.
Try it yourself
Change, predict, then run
Write template <typename T> T clamp_to(T value, T low, T high) and check it with clamp_to(5, 1, 10) and clamp_to(2.5, 0.0, 1.0). Then try clamp_to(5, 1, 10.0) and make it compile twice: once with an explicit template argument, once by changing only the literals.
Open the C++ workspaceCheck your understanding
Given template <typename T> T sum(T a, T b) { return a + b; }, why does double x = sum(1, 2.5); fail to compile?
- The assignment target is double, so deduction must produce a T that matches it and int gets in the way.
- Deduction yields int from the first argument and double from the second, and it will not convert either one to make them agree.
- A function template must be explicitly instantiated before any call to it is legal.
- a + b is ill-formed inside a template because int and double are unrelated types there.
Show answer
Deduction runs argument by argument, and every result for the same template parameter must be identical, so int versus double is a hard conflict rather than something a conversion smooths over. The first option is tempting because the target really is double, but the type you assign to plays no part in deduction at all; the proof is that sum<double>(1, 2.5) compiles, since fixing T explicitly stops deduction and lets 1 convert to 1.0.