C++ CTAD and Deduction Guides: Class Template Argument Deduction (C++17)
Key takeaways
CTAD lets you write std::pair p(1, 3.14) instead of std::pair<int, double>. The compiler turns each constructor into an implicit deduction guide; when a template parameter doesn't appear in the constructor, or you want a different type than the argument's, you add your own guide.
Introduction
Before C++17, class templates always needed explicit template arguments, which is why the standard library grew factory functions like std::make_pair and std::make_tuple: function templates could deduce types, class templates could not.
CTAD (Class Template Argument Deduction), introduced in C++17, closes that gap. The compiler deduces the template arguments from the constructor arguments:
// C++14
std::pair<int, double> p(1, 3.14);
auto p2 = std::make_pair(1, 3.14);
std::vector<int> vec = {1, 2, 3};
// C++17
std::pair p(1, 3.14); // pair<int, double>
std::vector vec = {1, 2, 3}; // vector<int>
How CTAD Works: Implicit Guides
For every constructor of the class template, the compiler invents a function template called an implicit deduction guide. For
template <typename T>
class Container {
T value;
public:
Container(T v) : value(v) {}
T get() const { return value; }
};
it behaves as if you had declared
template <typename T> Container(T) -> Container<T>;
Then it runs ordinary overload resolution and template argument deduction over those guides, as if they were functions being called with your arguments. The guide that wins determines the type.
Container c(42); // Container<int>
Container c2(3.14); // Container<double>
Container c3("Hello"); // Container<const char*>, not Container<std::string>
There is also a copy deduction candidate, template <typename T> Container(Container<T>) -> Container<T>, so copying an existing Container<int> deduces Container<int>, not Container<Container<int>>. You don’t need to write a guide for that.
In the standard library
#include <array>
#include <map>
#include <mutex>
#include <optional>
#include <tuple>
#include <vector>
int main() {
std::pair p(1, "Hello"); // pair<int, const char*>
std::tuple t(1, 2.0, "Hi"); // tuple<int, double, const char*>
std::vector vec = {1, 2, 3}; // vector<int>
std::array a{1, 2, 3, 4, 5}; // array<int, 5>
std::optional o(42); // optional<int>
std::map m{std::pair{"a", 1}, std::pair{"b", 2}}; // map<const char*, int>
std::mutex mtx;
std::lock_guard lock(mtx); // lock_guard<std::mutex>
}
std::lock_guard lock(mtx); is the case where CTAD pays off most in everyday code: the template argument is completely redundant with the argument.
Note the std::map line. The tempting std::map m = {{"a", 1}, {"b", 2}}; does not compile, because the inner {"a", 1} is a braced list with no type, and deduction can’t see through it to a pair<Key, T>. You need to give the elements a type, or write the map’s template arguments.
When the Implicit Guides Aren’t Enough
Implicit guides can only deduce a template parameter that appears in a constructor’s parameter list, in a deducible position. Two situations come up constantly.
A template parameter doesn’t appear in the constructor
The standard example is construction from an iterator range:
#include <iterator>
#include <vector>
template <typename T>
class MyVector {
public:
template <typename Iter>
MyVector(Iter first, Iter last) {
while (first != last) data_.push_back(*first++);
}
private:
std::vector<T> data_;
};
// Tell the compiler how to get T from Iter
template <typename Iter>
MyVector(Iter, Iter) -> MyVector<typename std::iterator_traits<Iter>::value_type>;
std::vector<int> source = {1, 2, 3, 4, 5};
MyVector v(source.begin(), source.end()); // MyVector<int>
The implicit guide for this constructor is template <typename T, typename Iter> MyVector(Iter, Iter) -> MyVector<T>, and T appears nowhere in the parameters, so it can never be deduced. The user-defined guide says how to compute it from Iter. std::vector, std::list, std::set and friends ship exactly this kind of guide, which is why std::vector v(arr.begin(), arr.end()); works.
You want a different type than the argument’s
#include <string>
template <typename T>
class Container {
T value;
public:
Container(T v) : value(std::move(v)) {}
};
Container(const char*) -> Container<std::string>;
Container c("hello"); // Container<std::string>
A string literal is a const char[6] that decays to const char*, and storing a const char* is rarely what you want: the object would hold a pointer to someone else’s buffer. The guide redirects that case to std::string. User-defined guides also win ties against implicit ones, so the guide is chosen even though the implicit Container(T) guide also matches.
A guide can be as targeted as you like. This Pair converts either or both string literals:
#include <iostream>
#include <string>
template <typename A, typename B>
class Pair {
public:
A first;
B second;
Pair(A a, B b) : first(std::move(a)), second(std::move(b)) {}
};
Pair(const char*, const char*) -> Pair<std::string, std::string>;
template <typename T> Pair(const char*, T) -> Pair<std::string, T>;
int main() {
Pair p1(42, 3.14); // Pair<int, double> (implicit guide)
Pair p2("hello", "world"); // Pair<std::string, std::string>
Pair p3("name", 42); // Pair<std::string, int>
Pair p4 = p1; // Pair<int, double> (copy deduction candidate)
std::cout << p3.first << ": " << p3.second << '\n'; // name: 42
}
Aggregates (before C++20)
A plain aggregate has no constructors, so in C++17 it has no implicit guides either:
template <typename T>
struct Named {
T value;
std::string name;
};
Named n{42, std::string("answer")}; // C++17: error without a guide; C++20: Named<int>
template <typename T> Named(T, std::string) -> Named<T>; // needed only for C++17
C++20 added aggregate deduction (GCC 10+, Clang 17+, MSVC 19.27+), so on a C++20 compiler the guide is unnecessary. C++20 also allows CTAD through alias templates.
Guide syntax
template <template-params> // omit for a non-template guide
explicit(optional) ClassName(parameters) -> ClassName<deduced-arguments>;
A guide lives at namespace scope next to the class. It’s only consulted during deduction; after the type is chosen, the object is constructed with the ordinary constructors, so a guide can never make an unconstructible object constructible.
Explicit Guides
Marking a guide explicit means it is ignored in copy-initialization (T x = ...;) and only used for direct-initialization:
#include <string>
template <typename T>
struct Id {
template <typename U>
Id(U u) : v(u) {}
T v;
};
explicit Id(const char*) -> Id<std::string>;
Id a("x"); // OK: Id<std::string>
auto b = Id{"x"}; // OK: Id{...} is direct-initialization
// Id c = "x"; // error: the explicit guide is not considered, and nothing else can deduce T
Be careful about what this really prevents. An explicit guide only switches itself off; if an implicit guide from a non-explicit constructor also matches, copy-initialization simply uses that one instead. In the example above, the constructor’s implicit guide can’t deduce T, so the explicit guide is the only path and copy-initialization fails. With a plain Wrapper(T) constructor, adding explicit Wrapper(T) -> Wrapper<T>; changes nothing at all. If you want to forbid Wrapper w = 42;, make the constructor explicit.
Common Pitfalls
Braces vs parentheses
std::vector v1(5, 1); // vector<int> with five 1s
std::vector v2{5, 1}; // vector<int> with elements 5 and 1
CTAD follows the same rule as ordinary construction: braces prefer the initializer_list constructor. The type is the same here, but the contents differ.
Copy wins over wrapping
std::vector<int> a{1, 2, 3};
std::vector b{a}; // vector<int>, a copy of a
std::vector c{a, a}; // vector<vector<int>>
With a single argument of the same template, the copy deduction candidate is preferred over the initializer_list guide, so std::vector b{a} is a copy, not a vector containing a. Generic code that does std::vector result{x}; expecting “a vector of one element” gets a different type when x happens to be a vector. The fix is to spell the type out: std::vector<T> result{x};.
String literals deduce const char*
std::pair p(1, "Hello"); // pair<int, const char*>
std::vector names{"a", "b"}; // vector<const char*>
The standard library doesn’t provide the const char* to std::string redirect for its own types. If you want strings, say so: std::vector<std::string> names{"a", "b"}; or use the "a"s literal from std::string_literals.
Ambiguous guides
template <typename T>
class Box {
public:
Box(T, T) {}
Box(T, int) {}
};
template <typename T> Box(T, T) -> Box<T>;
template <typename T> Box(T, int) -> Box<T>;
Box b1(1.5, 2.5); // OK: Box(T, T) is the better match (the other needs double -> int), Box<double>
// Box b2(1, 2); // error: ambiguous, both guides match with T = int
With arguments (1, 2) both guides deduce T = int equally well and neither is more specialized, so deduction fails. Writing Box<int> explicitly doesn’t rescue it either: instantiating Box<int> gives two constructors with the identical signature (int, int), which is a hard error. The guide ambiguity is a symptom of a class that is itself broken for T = int, and that is typical: when you find yourself fighting guide ambiguity, look at the constructor set first.
CTAD is all or nothing
// std::pair<int> p(1, 2.0); // error: can't mix explicit and deduced arguments
std::pair<int, double> p(1, 2.0); // OK
std::pair p2(1, 2.0); // OK
Unlike function templates, you can’t provide the first template argument and let the compiler deduce the rest.
When NOT to Write a Deduction Guide
When it repeats the constructor. This guide is pure noise, because the implicit guide from Box(T) already says the same thing:
template <typename T> Box(T) -> Box<T>; // redundant
The same goes for ArrayView(T*, size_t) -> ArrayView<T> when the constructor is ArrayView(T*, size_t). Only write a guide where you can say what it changes.
When the conversion would surprise callers. A guide that silently turns int into long, or float into double, makes Foo f(x); produce a type nobody expected from reading the call. Keep guides to the well-known cases (literal to string, iterator to value type, pointer to pointee) and require explicit template arguments for everything else.
When a parameter shouldn’t participate in deduction. Sometimes you want deduction from one argument only:
template <typename T> struct type_identity { using type = T; }; // std::type_identity in C++20
template <typename T>
struct Scaled {
Scaled(T value, typename type_identity<T>::type factor) : v(value * factor) {}
T v;
};
Scaled s(2.5, 2); // Scaled<double>: T comes from the first argument only
Without type_identity, Scaled(2.5, 2) would deduce T = double from one argument and T = int from the other and fail. Putting the second parameter in a non-deduced context says “convert this to whatever T is”.
When the class wasn’t designed for CTAD. If a type’s constructors don’t make the template arguments obvious, CTAD at call sites makes the code harder to read. Clang has a warning, -Wctad-maybe-unsupported, that flags CTAD on classes without any user-defined guide, which some codebases enable to keep CTAD limited to types that opted in. The idiom for opting in without changing deduction is an empty guide such as template <typename T> Box(typename T::Tag) -> Box<T>; that never matches in practice.
When you still support C++14. Keep a make_xxx factory function; CTAD and guides require C++17.
Deduce at the call site, or spell the type?
Use CTAD where the type is obvious from the arguments (std::lock_guard, std::pair, std::array), and write the template arguments when the reader would otherwise have to work out the deduction rules to know what type they have. The pitfalls above are the test: if a call involves braces around a single container, a string literal, or a user-defined guide that changes the type, the reader has to know the rules to predict the result, and writing the arguments out is cheaper than that reasoning.