Class Template Argument Deduction (CTAD)
C++17 CTAD deduces class template arguments from constructors. Covers deduction guides, aggregate and alias template CTAD (C++20), and common pitfalls.
Class Template Argument Deduction (CTAD)since C++17CTAD allows the compiler to infer all template arguments of a class template from the types of constructor arguments at the point of variable initialization, eliminating explicit angle-bracket arguments.
Overview
Before C++17, function templates had argument deduction but class templates required every template argument spelled out explicitly. CTAD closes this gap by extending the same deduction machinery to class templates.
When the compiler sees ClassName{args...} without explicit template arguments, it synthesizes a set of deduction candidates β imaginary function templates, one per constructor β and runs overload resolution on them. The winning candidate's deduced template arguments are substituted into the class template to produce the concrete type.
template<typename T>
struct Box {
T value;
Box(T v) : value(v) {} // compiler synthesizes:
// template<typename T> Box<T> __guide(T v);
};
Box b1{42}; // Box<int>
Box b2{3.14}; // Box<double>
Box b3{"hi"}; // Box<const char*>
// Deduction commits at initialization β type is fixed from that point
auto b = Box{42}; // Box<int>
b = Box{3.14}; // ERROR: cannot assign Box<double> to Box<int>CTAD applies in variable declarations and new expressions. It does not apply when any template argument is explicitly provided β even a single explicit argument disables CTAD entirely for that instantiation.
Syntax
Implicit deduction guides
The compiler generates one imaginary function template per constructor automatically. No user action required. For most straightforward cases (constructor parameters directly name the template parameters), this is sufficient.
User-defined deduction guides
// Looks like a function declaration with trailing return type β no body, no auto
template<typename T>
ClassName(param-type) -> ClassName<deduced-specialization>;Guides participate in overload resolution alongside the implicit guides. They can be plain functions or function templates. Marking a guide explicit restricts it to direct-initialization contexts, mirroring the behavior of explicit constructors.
Since C++20, guides accept requires constraints to steer deduction based on type properties:
template<typename T>
struct Num { Num(T); };
// C++20: constrained guide β promotes integral arguments to long long
template<typename T> requires std::integral<T>
Num(T) -> Num<long long>;
Num n1{42}; // Num<long long> β constraint satisfied, guide wins
Num n2{3.14}; // Num<double> β constraint fails, falls back to implicit guideExamples
Standard library: common cases
#include <utility>
#include <tuple>
#include <vector>
#include <array>
#include <mutex>
std::pair p{1, 2.5}; // pair<int, double> β C++17
std::tuple t{1, 2.0, "three"}; // tuple<int, double, const char*> β C++17
std::vector v1{1, 2, 3}; // vector<int> β C++17
std::vector v2(v1.begin(), v1.end()); // vector<int> via iterator-pair guide
// std::array deduces both T and N via an explicit pack guide in the standard:
// template<class T, class... U>
// array(T, U...) -> array<T, 1 + sizeof...(U)>;
std::array a{1, 2, 3, 4, 5}; // array<int, 5> β C++17
// std::array bad{1, 2.0}; // ERROR β int vs double, pack requires uniform type
std::mutex mtx;
std::lock_guard guard{mtx}; // lock_guard<mutex> β C++17
std::unique_lock lock{mtx}; // unique_lock<mutex> β C++17
std::scoped_lock sl{mtx}; // scoped_lock<mutex> β C++17Smart pointers have no CTAD by design. std::unique_ptr and std::shared_ptr omit deduction guides because a T* argument is ambiguous between a pointer-to-single-object and a pointer-to-array, each requiring a different deleter type. std::make_unique and std::make_shared remain the correct idiom.
User-defined guides to collapse variadic packs
#include <numeric>
template<typename... Ts>
struct Sum {
std::common_type_t<Ts...> value;
Sum(Ts... vals) : value((vals + ...)) {}
};
// Without a guide, Sum{1, 2.0, 3.0f} deduces Sum<int, double, float> β rarely useful.
// Collapse all arguments to their common type:
template<typename... Ts>
Sum(Ts&&...) -> Sum<std::common_type_t<Ts...>>; // C++17
Sum s{1, 2.0, 3.0f}; // Sum<double>Guides also let you deduce element type through a container rather than the container itself:
template<typename T>
class Stats {
public:
double mean, stddev;
explicit Stats(const std::vector<T>& v) { /* compute */ }
};
template<typename T>
Stats(const std::vector<T>&) -> Stats<T>; // C++17 β peel the vector
std::vector<float> data{1.f, 2.f, 3.f};
Stats s{data}; // Stats<float>, not Stats<std::vector<float>>Aggregate CTAD (C++20)
C++17 CTAD only works for types with at least one user-provided constructor β aggregates were excluded. C++20 generates aggregate deduction candidates from member types automatically.
// C++20: pure aggregate, no constructors needed
template<typename T, typename U>
struct Pair {
T first;
U second;
};
Pair p{1, 3.14}; // Pair<int, double> β C++20 only
Pair p2{"key", 42LL}; // Pair<const char*, long long>
// C++17 required an explicit guide or spelled-out arguments:
// template<typename T, typename U> Pair(T, U) -> Pair<T, U>; // C++17 workaroundC++20 aggregate CTAD enables the clean overload-visitor pattern for std::variant without any manual guides:
// C++20 β aggregate CTAD makes the explicit deduction guide unnecessary
template<typename... Ts>
struct overload : Ts... { using Ts::operator()...; };
std::variant<int, double, std::string> v{3.14};
std::visit(overload{
[](int x) { /* handle int */ },
[](double x) { /* handle double */ },
[](const std::string& s){ /* handle string */ },
}, v);Alias template CTAD (C++20)
template<typename T>
using Vec = std::vector<T>;
// C++17: CTAD does not propagate through alias templates
// Vec v{1, 2, 3}; // ERROR in C++17
// C++20: deduction guides are projected through the alias
Vec v{1, 2, 3}; // vector<int> β C++20 only
template<typename K, typename V>
using OrderedMap = std::map<K, V>;
// C++20
OrderedMap m{std::pair{"a", 1}, std::pair{"b", 2}}; // map<const char*, int>Best Practices
Use CTAD freely in function bodies where the initializer makes the type self-evident. It reduces noise without sacrificing clarity.
Spell out types at API boundaries. Return types and parameter types in headers should be explicit β CTAD cannot apply to return types anyway, and explicit types document the contract:
// Unclear: what does this return? Reader must know CTAD rules to infer it.
auto make_range_stats(const std::vector<double>& v) {
return std::pair{v.front(), v.back()};
}
// Better: the return type is part of the interface
std::pair<double, double> make_range_stats(const std::vector<double>& v) {
return {v.front(), v.back()};
}Write explicit guides for any non-trivial mapping between constructor parameters and template parameters. Implicit guides only handle the case where constructor parameter types directly name template parameters β everything else needs a guide to make intent explicit and deduction deterministic.
Common Pitfalls
Braces still prefer initializer_list β CTAD does not change this
std::vector v1{3, 0}; // vector<int> with two elements: {3, 0} β NOT 3 zeros
std::vector v2(3, 0); // vector<int> with 3 zeros β parentheses bypass initializer_listconst char* deduction from string literals
std::vector v{"a", "b", "c"}; // vector<const char*> β almost never intended
std::vector<std::string> vs{"a","b","c"}; // explicit type required for string elementsCTAD requires an initializer
std::vector v; // ERROR β no arguments, nothing to deduce from
std::vector<int> v; // OK β explicit type, default-constructedAll-or-nothing β partial deduction is ill-formed
template<typename T, typename Alloc = std::allocator<T>>
class Pool { /* ... */ };
Pool p{1, 2, 3}; // OK β fully deduced
Pool<int> p2{1, 2, 3}; // OK β fully explicit (Alloc gets default)
// Pool<int, CustomAlloc> p3{1,2,3}; // OK β fully explicit
// There is no way to fix one argument and let CTAD deduce the rest.
// If any angle-bracket list is present, all arguments must be provided (or defaulted).explicit deduction guides restrict copy-init
template<typename T>
struct Safe {
explicit Safe(T v);
};
template<typename T>
explicit Safe(T) -> Safe<T>; // mirrors the explicit constructor
Safe s1{42}; // OK β direct-list-init
Safe s2 = 42; // ERROR β explicit guide excluded from copy-init
Safe s3 = Safe{42}; // OK β explicit on the right-hand sideStandard Library CTAD Examples
The standard library ships deduction guides for the containers and utilities you use every day. Knowing which types support CTAD β and which deliberately omit it β prevents silent surprises.
// C++17
#include <vector>
#include <array>
#include <utility>
#include <tuple>
#include <optional>
#include <mutex>
std::vector v{1, 2, 3}; // vector<int>
std::pair p{1, "hello"}; // pair<int, const char*>
std::tuple t{1, 2.0, "three"}; // tuple<int, double, const char*>
std::optional o{42}; // optional<int>
std::array arr{1, 2, 3}; // array<int, 3> β T and N both deduced
// std::array bad{1, 2.0}; // ERROR β all elements must share one type
std::mutex m;
std::lock_guard lg{m}; // lock_guard<mutex>
std::unique_lock ul{m}; // unique_lock<mutex>
std::scoped_lock sl{m}; // scoped_lock<mutex>std::unique_ptr and std::shared_ptr intentionally carry no deduction guides. A raw T* argument is ambiguous between a single-object pointer and an array pointer, each needing a different deleter. Use std::make_unique and std::make_shared instead β they perform type deduction at the function level where the distinction can be made explicit.
// C++17
auto up = std::make_unique<int>(5); // unique_ptr<int> β make_unique deduces T
auto sp = std::make_shared<int>(5); // shared_ptr<int> β same pattern
// std::unique_ptr up2{new int{5}}; // ERROR β no deduction guideUser-Defined Deduction Guides
Deduction guides are function-like declarations β no body, no auto keyword β that map constructor argument types to a concrete class template specialization. They are placed in the same namespace as the class template and participate in overload resolution alongside the compiler-generated implicit guides.
When implicit guides are insufficient β for example when the constructor argument type wraps the template parameter rather than naming it directly β a user-defined guide fills the gap:
// C++17
template<typename T>
class MyContainer {
std::vector<T> data_;
public:
// Constructor takes initializer_list β implicit guide would deduce
// MyContainer<initializer_list<T>>, not MyContainer<T>
MyContainer(std::initializer_list<T> il) : data_(il) {}
};
template<typename T>
MyContainer(std::initializer_list<T>) -> MyContainer<T>; // explicit guide
MyContainer c{1, 2, 3}; // MyContainer<int> β guide peels the initializer_listGuides also apply to multi-parameter cases where the implicit guide would deduce an unhelpful pair of independent types:
// C++17
template<typename T, typename U>
struct MyPair {
T first;
U second;
MyPair(T f, U s) : first(f), second(s) {}
};
template<typename T, typename U>
MyPair(T, U) -> MyPair<T, U>; // explicit guide β mirrors the constructor exactly
MyPair mp{42, 3.14}; // MyPair<int, double>C++20 allows deduction guides on aggregate types that have no constructors at all. The compiler generates aggregate deduction candidates from the member list; a user-defined guide can still override or supplement that behaviour:
// C++20 β aggregate: no constructor required
template<typename T>
struct Wrapper {
T val;
};
// Optional explicit guide β aggregate CTAD already works without it in C++20
template<typename T>
Wrapper(T) -> Wrapper<T>;
Wrapper w{42}; // Wrapper<int>CTAD with Inheritance and Base Classes
CTAD deduction does not propagate through inheritance. When a derived class is constructed, the compiler synthesizes deduction candidates from the derived class's constructors, not from the base class template parameters. The base's template arguments must be fixed at the point the derived class is defined.
// C++17
#include <vector>
#include <string>
class StringVector : public std::vector<std::string> {
public:
using std::vector<std::string>::vector; // inherit constructors
};
// StringVector is not a template β there is nothing to deduce
StringVector sv{"a", "b", "c"}; // OK, but no CTAD involved; T is fixed as std::string
// You cannot deduce the base template argument from the derived type:
// template<typename T>
// class TypedVector : public std::vector<T> { /* ... */ };
// TypedVector tv{1, 2, 3}; // ERROR β no guide maps {int...} to TypedVector<int>The workaround for a thin wrapper that should carry its own CTAD is to write an explicit deduction guide for the derived template:
// C++17 β wrapper with an explicit guide
template<typename T>
class TypedVector : public std::vector<T> {
public:
using std::vector<T>::vector;
};
template<typename T>
TypedVector(std::initializer_list<T>) -> TypedVector<T>;
TypedVector tv{1, 2, 3}; // TypedVector<int> β guide makes deduction possibleCTAD runs on the constructed type, never on the base. Assigning a derived object to a base variable still deduces the base's template argument from the assignment, not from any guide on the derived class.
CTAD for Aggregates (C++20)
C++17 CTAD was restricted to types with at least one user-provided constructor. Pure aggregates β structs with only public non-static data members and no user-declared constructors β were left out. C++20 removes this restriction by generating aggregate deduction candidates automatically from the member list.
For a simple non-template aggregate the language already supported brace-initialization before C++17; the C++20 change matters for class template aggregates:
// C++20 β aggregate class template, no constructors
template<typename T>
struct Wrapper {
T val;
};
Wrapper w{42}; // Wrapper<int> β C++20 aggregate deduction
Wrapper wf{3.14f}; // Wrapper<float>
// C++17: ERROR β no constructors, so no deduction candidates were generated
template<typename T, typename U>
struct Pair {
T first;
U second;
};
Pair p{1, 3.14}; // Pair<int, double> β C++20
Pair p2{"key", 42LL}; // Pair<const char*, long long>The compiler synthesises a candidate equivalent to a constructor with one parameter per member in declaration order. Default member initialisers are honoured: if a member has a default, its corresponding position in the candidate is given a default argument, making it optional in the braced argument list.
// C++20
template<typename T>
struct Config {
T value;
bool verbose = false; // default member initializer
};
Config c1{42}; // Config<int> β verbose defaults to false
Config c2{42, true}; // Config<int> β verbose explicitly setAlias Template CTAD (C++20)
C++17 CTAD did not propagate through alias templates. An alias is transparent to the type system for most purposes, but the C++17 wording omitted the machinery needed to project deduction guides through an alias. C++20 closes this gap.
template<typename T>
using Vec = std::vector<T>;
// C++17: alias CTAD not supported
// Vec v{1, 2, 3}; // ERROR in C++17 β cannot deduce T in Vec<T>
// C++20: deduction guides are projected through the alias automatically
Vec v{1, 2, 3}; // std::vector<int> β C++20The projection works by rewriting the deduction problem: the compiler maps the alias arguments through the alias definition to recover the underlying template, runs CTAD on the underlying template, then maps the result back through the alias. User-defined guides on the underlying template are projected as well.
// C++20
template<typename K, typename V>
using OrderedMap = std::map<K, V>;
OrderedMap m{std::pair{"alpha", 1}, std::pair{"beta", 2}};
// deduces std::map<const char*, int>
template<typename T>
using NumVec = std::vector<T>;
NumVec nv{1.0f, 2.0f, 3.0f}; // std::vector<float>Non-deducible aliases β those that fix one or more template parameters β do not gain CTAD; there is simply nothing to deduce:
// C++20
using StringVec = std::vector<std::string>; // not a template alias β no CTAD needed
StringVec sv{"a", "b"}; // works via std::string constructors as beforeCTAD vs auto: When to Use Each
CTAD and auto are complementary, not competing. CTAD deduces the template arguments of a named class template; auto deduces the full type of an expression. Choosing between them depends on how much of the type you want to make visible at the declaration site.
| Mechanism | When it applies | Type visible in source | Prevents implicit conversions |
|---|---|---|---|
| Explicit type | Always | Fully | Yes |
| CTAD | Constructing a class template without auto | Class name visible; args deduced | Yes β deduced args are fixed |
auto | Initialising from any expression | No | No β accepts any compatible type |
auto + CTAD | auto x = Template{args...} | No | No |
Use auto when the initialiser expression already makes the type clear and the exact type does not need to be part of the documentation:
// C++17
auto it = v.begin(); // iterator type is obvious from context
auto res = compute(); // type follows from the function signatureUse CTAD when you are constructing a known template type and want the class name visible at the call site without repeating the template arguments:
// C++17
std::lock_guard guard{mtx}; // class name communicates intent; <mutex> is noise
std::pair p{1, 2.5}; // pair is meaningful; <int, double> is mechanicalCombine both when you want neither the template arguments nor the variable type to appear in the code:
// C++17
auto v = std::vector{1, 2, 3}; // vector<int> β CTAD deduces <int>, auto hides it
auto p = std::pair{std::string{"k"}, 42}; // pair<string, int>Prefer CTAD over auto alone at API boundaries inside a function body when the class name itself conveys semantic meaning. Reserve explicit types for function signatures, return types, and any declaration where the type is part of the documented contract.
See Also
- auto type deduction β the function template deduction rules that CTAD builds on
- Templates β class template fundamentals and instantiation
- Concepts β constraining deduction guides with
requires(C++20) - Structured bindings β companion C++17 deduction feature