Skip to main content

Template Argument Deduction

Template argument deduction lets the compiler figure out template parameters from function arguments automatically. No need to write func<int>(5) when func(5) works!

Compiler Figures It Out

The compiler examines function arguments and deduces template parameters. Usually just works, but understanding the rules helps debug errors.

Same rules power auto

auto deduction is template argument deduction in disguise — auto x = expr; deduces x exactly as a template<class T> f(T) would deduce T. So the rules here also explain auto; the one exception is auto with braced initializers, noted on that page.

Basic Deduction

template<typename T>
void func(T param) {
// ...
}

func(5); // T deduced as int
func(3.14); // T deduced as double
func("hello"); // T deduced as const char*

std::string s = "world";
func(s); // T deduced as std::string

Compiler matches argument types to template parameters automatically.

Reference Deduction

lvalue reference:

template<typename T>
void func(T& param) {
// ...
}

int x = 42;
const int cx = x;

func(x); // T = int, param type is int&
func(cx); // T = const int, param type is const int&
// func(5); // Error: can't bind rvalue to lvalue ref

const reference:

template<typename T>
void func(const T& param) {
// ...
}

func(x); // T = int, param = const int&
func(cx); // T = int, param = const int& (const is redundant)
func(5); // OK: const ref binds to rvalue

rvalue reference (forwarding reference):

template<typename T>
void func(T&& param) { // Universal/forwarding reference
// ...
}

int x = 42;
func(x); // lvalue: T = int&, param = int&
func(42); // rvalue: T = int, param = int&&
func(std::move(x)); // rvalue: T = int, param = int&&

Pointer Deduction

template<typename T>
void func(T* param) {
// ...
}

int x = 42;
const int cx = 100;

func(&x); // T = int, param = int*
func(&cx); // T = const int, param = const int*

Array Deduction

template<typename T>
void func(T param) {
// ...
}

int arr[10];
func(arr); // T = int* (array decays to pointer)

// With reference: preserves array type!
template<typename T>
void func2(T& param) {
// ...
}

func2(arr); // T = int[10], param = int(&)[10]

// Deduce array size
template<typename T, size_t N>
size_t arraySize(T (&)[N]) {
return N;
}

int arr[15];
size_t size = arraySize(arr); // N = 15

const and Reference Stripping

template<typename T>
void func(T param) { // Pass by value
// ...
}

int x = 42;
const int cx = x;
const int& rx = x;

func(x); // T = int (copy, const ignored)
func(cx); // T = int (copy, const stripped)
func(rx); // T = int (copy, reference and const stripped)

When passing by value, top-level const and references are stripped because you're getting a copy anyway.

Multiple Parameters

template<typename T>
void func(T a, T b) { // Both must be same type
// ...
}

func(5, 10); // T = int
// func(5, 3.14); // Error: conflicting types (int vs double)

// Solution: Multiple template parameters
template<typename T, typename U>
void func2(T a, U b) {
// ...
}

func2(5, 3.14); // T = int, U = double

Explicit Template Arguments

template<typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}

auto x = max(5, 10); // T = int
auto y = max<double>(5, 10); // Force T = double (converts args)

// Can specify some, deduce others
template<typename R, typename T>
R convert(T value) {
return static_cast<R>(value);
}

int x = 42;
double d = convert<double>(x); // R = double (specified), T = int (deduced)

Return Type Deduction

// C++11: Trailing return with decltype
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}

// C++14: Auto return type deduction
template<typename T, typename U>
auto add(T a, U b) {
return a + b; // Return type deduced from expression
}

auto x = add(5, 3.14); // Returns double

Class Template Argument Deduction (CTAD - C++17)

template<typename T>
class Pair {
public:
T first, second;
Pair(T a, T b) : first(a), second(b) {}
};

// Before C++17
Pair<int> p1(10, 20);

// C++17: Deduction from constructor
Pair p2(10, 20); // Deduced as Pair<int>
Pair p3(3.14, 2.71); // Deduced as Pair<double>

// std::vector example
std::vector v{1, 2, 3, 4, 5}; // Deduced as vector<int>

Deduction Guides (C++17)

Help the compiler deduce template arguments:

template<typename T>
class Container {
public:
Container(T value) { /* ... */ }
};

// Deduction guide
template<typename T>
Container(T) -> Container<T>;

Container c(42); // Deduced as Container<int>

// For const char*
Container(const char*) -> Container<std::string>;
Container c2("hello"); // Deduced as Container<std::string>

Common Deduction Failures

template<typename T>
void func(T a, T b) {
// ...
}

// Type mismatch
// func(5, 3.14); // int vs double

// Can't deduce from return type alone
template<typename T>
T convert(int value) {
return static_cast<T>(value);
}
// auto x = convert(42); // Can't deduce T
auto x = convert<double>(42); // Must specify

// Non-deduced context
template<typename T>
void func(typename std::vector<T>::iterator it) {
// ...
}
// Can't deduce T from iterator type (nested dependent type)

Perfect Forwarding

template<typename T>
void wrapper(T&& arg) {
// Forward preserving value category
actualFunction(std::forward<T>(arg));
}

int x = 42;
wrapper(x); // T = int&, forwards lvalue
wrapper(42); // T = int, forwards rvalue
wrapper(std::move(x)); // T = int, forwards rvalue
Deduction Rules Summary

By value = strips const and references
By reference = preserves const, deduces reference
By pointer = deduces pointer, preserves const
Array = decays to pointer unless reference parameter
Universal ref (T&&) = forwards lvalue/rvalue correctly
CTAD (C++17) = deduce class template args from constructor
Explicit args = can override deduction
Multiple params = all T must match unless separate template params