C++ Deduction Guides: Customizing CTAD in C++17
이 글의 핵심
Write deduction guides to customize C++17 CTAD: transform types at deduction time, resolve ambiguous constructors, match STL patterns, and avoid common pitfalls with initializer_list and explicit guides.
What Is Class Template Argument Deduction?
Before C++17, you always had to spell out template arguments explicitly when constructing a class template:
// C++14 and earlier — must write <int>
std::pair<int, double> p(42, 3.14);
std::vector<std::string> v{"a", "b", "c"};
// Or use factory functions that could deduce for you
auto p = std::make_pair(42, 3.14); // deduces pair<int, double>
C++17 introduced Class Template Argument Deduction (CTAD): the compiler deduces template arguments from constructor call arguments, just as it deduces function template arguments:
// C++17 — compiler deduces template arguments
std::pair p(42, 3.14); // pair<int, double>
std::vector v{"a", "b", "c"}; // vector<const char*>
CTAD uses the class’s constructors as a set of “implicit deduction guides.” When the implicit guides don’t produce the right type, you write an explicit deduction guide.
The Basic Syntax
A deduction guide is a top-level declaration in the class template’s namespace:
template<typename T>
class Box {
public:
Box(T value) : value_(value) {}
private:
T value_;
};
// Deduction guide: when constructed from T, deduce Box<T>
// This matches what the constructor already does — so it's redundant here
// but shows the syntax
template<typename T>
Box(T) -> Box<T>;
Box b(42); // Box<int>
Box s("hello"); // Box<const char*>
The syntax is: template-header class-name(parameters) -> class-name<deduced-args> ;
Why the Implicit Guides Aren’t Always Enough
Type Transformation
The most common reason to write a guide: transform a constructor argument type into a better template argument:
template<typename T>
class Buffer {
public:
Buffer(const char* s) : data_(s) {} // constructor takes const char*
private:
T data_;
};
// Without a guide: Buffer("hello") would deduce Buffer<???> — no T to deduce from
// With a guide: convert const char* to std::string
Buffer(const char*) -> Buffer<std::string>;
Buffer b("hello"); // Buffer<std::string>, not Buffer<const char*>
Without the guide, the constructor Buffer(const char*) provides no way for the compiler to know what T should be — it cannot appear in the constructor signature at all. The guide fills that gap.
Iterator Pair Pattern
The STL containers use this for range construction:
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_;
};
// Guide: deduce T from the iterator's value_type
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>
Without the guide, T cannot be deduced from Iter, Iter — the compiler only knows about Iter, not what *Iter produces. The guide extracts value_type from the iterator traits.
Complete Working Example
A Pair class that deduces element types and converts string literals to std::string:
#include <string>
#include <iostream>
#include <type_traits>
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)) {}
void print() const {
std::cout << "(" << first << ", " << second << ")\n";
}
};
// Guide 1: standard deduction from constructor types
template<typename A, typename B>
Pair(A, B) -> Pair<A, B>;
// Guide 2: convert const char* arguments to std::string
Pair(const char*, const char*) -> Pair<std::string, std::string>;
// Guide 3: mixed — first is const char*, second is some type T
template<typename T>
Pair(const char*, T) -> Pair<std::string, T>;
int main() {
Pair p1(42, 3.14); // Pair<int, double> via Guide 1
Pair p2("hello", "world"); // Pair<string, string> via Guide 2
Pair p3("name", 42); // Pair<string, int> via Guide 3
p1.print(); // (42, 3.14)
p2.print(); // (hello, world)
p3.print(); // (name, 42)
// Copy construction — must not loop
Pair p4 = p1; // Pair<int, double>
p4.print(); // (42, 3.14)
}
Explicit Deduction Guides
The explicit keyword on a deduction guide prevents implicit conversion — the guide only fires when the constructor arguments are provided directly, not in copy-initialization:
template<typename T>
class Wrapper {
public:
explicit Wrapper(T val) : val_(val) {}
T val_;
};
// explicit guide: only fires for direct initialization
template<typename T>
explicit Wrapper(T) -> Wrapper<T>;
Wrapper w1(42); // OK — direct initialization
// Wrapper w2 = 42; // ERROR — explicit guide prevents this
// auto w3 = Wrapper{42}; // OK — brace direct init works
// Without explicit, both forms would work
Use explicit guides when implicit construction from that argument type would be surprising or unsafe.
Common Patterns
Array + Size
template<typename T>
class ArrayView {
T* data_;
size_t size_;
public:
ArrayView(T* data, size_t size) : data_(data), size_(size) {}
};
// Deduce T from the pointer type
template<typename T>
ArrayView(T*, size_t) -> ArrayView<T>;
int arr[] = {1, 2, 3, 4, 5};
ArrayView view(arr, 5); // ArrayView<int>
Smart Pointer Style
template<typename T>
class Handle {
T* ptr_;
public:
explicit Handle(T* p) : ptr_(p) {}
~Handle() { delete ptr_; }
};
template<typename T>
Handle(T*) -> Handle<T>;
Handle h(new int(42)); // Handle<int>
Conditional Element Type (C++17 if constexpr)
template<typename T>
class NumericBox {
public:
// Store float for integral types (promotes precision)
using StoredType = std::conditional_t<std::is_integral_v<T>, float, T>;
StoredType value;
NumericBox(T v) : value(static_cast<StoredType>(v)) {}
};
// Guide: integral types become float, others pass through
template<typename T>
NumericBox(T) -> NumericBox<T>;
NumericBox nb(42); // NumericBox<int>, but stored as float
NumericBox nd(3.14); // NumericBox<double>
Pitfalls
Ambiguous Guides
When two guides match equally well, CTAD fails:
template<typename T>
class Box {
public:
Box(T a, T b) {}
Box(T a, int b) {}
};
template<typename T> Box(T, T) -> Box<T>; // Guide 1
template<typename T> Box(T, int) -> Box<T>; // Guide 2
// Box b(1, 2); // ERROR: ambiguous — both guides match with T=int
Fix: make one guide more specific, or use explicit template arguments: Box<int> b(1, 2).
Copy Constructor Conflict
When you define guides, CTAD may confuse copy construction with other construction:
template<typename T>
class Container {
public:
Container(T value) {}
Container(const Container&) = default; // copy constructor
};
template<typename T>
Container(T) -> Container<T>;
Container<int> c1(42);
Container c2 = c1; // should copy-construct Container<int>
// guide would give Container<Container<int>> — wrong!
Fix: add a guide that preserves the type when constructing from the same template:
template<typename T>
Container(Container<T>) -> Container<T>; // copies stay the same type
initializer_list Priority
Brace initialization preferentially binds to initializer_list constructors, which can suppress CTAD:
template<typename T>
class MyList {
public:
MyList(std::initializer_list<T> items) {}
MyList(T a, T b) {}
};
MyList ml1{1, 2}; // initializer_list<int> — clear
MyList ml2(1, 2); // (int, int) constructor
// MyList ml3{1, 2.0}; // may be ambiguous — prefer explicit types
When brace initialization might be ambiguous, use parentheses or explicit template arguments.
Standard Library Deduction Guides
The C++17 standard added deduction guides to common containers so CTAD works naturally:
// std::vector guide (iterator pair)
std::vector v(arr.begin(), arr.end()); // deduces vector<int>
// std::array guide
std::array a{1, 2, 3, 4, 5}; // deduces array<int, 5>
// std::pair guide
std::pair p(42, "hello"); // pair<int, const char*>
// std::optional guide
std::optional o(42); // optional<int>
// std::tuple guide
std::tuple t(1, 2.0, "three"); // tuple<int, double, const char*>
When NOT to Use Deduction Guides
If the default CTAD already works: adding a guide that does the same thing as the implicit one is noise.
// Redundant — compiler already deduces this from the constructor
template<typename T>
Box(T) -> Box<T>; // unnecessary if Box has Box(T val) constructor
If the transformation is surprising: a guide that converts int to long silently would confuse users. Make surprising conversions explicit (either explicit guides or factory functions).
Pre-C++17 compatibility: deduction guides require C++17. If you need to support older standards, stick to factory functions:
// make_pair style factory — works in C++11/14
template<typename A, typename B>
Pair<A, B> make_pair(A a, B b) { return Pair<A, B>(std::move(a), std::move(b)); }
Key Takeaways
- Deduction guides customize how CTAD maps constructor arguments to template arguments
- Syntax:
template<...> ClassName(params) -> ClassName<deduced-args>;in the enclosing namespace - Common uses: type transformation (
const char*→std::string), iterator-range deduction (extractvalue_type), pointer-to-pointee deduction explicitguides prevent implicit construction — use for guides that would otherwise allow surprising conversions- Copy constructor conflict: add a
ClassName(ClassName<T>) -> ClassName<T>guide when the generic guide would wrap instead of copy initializer_listpriority: brace init prefersinitializer_listconstructors — use parentheses when you need the other constructor- Compile-time only: deduction guides have zero runtime cost
- Standard library:
std::vector,std::array,std::pair,std::tuple, etc. all have C++17 guides — rely on them
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Deduction guides for CTAD: syntax, iterator pairs, const char* to string conversions, explicit guides, and pitfalls with… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 템플릿 템플릿 인자 | template template parameter 가이드
- C++ CTAD | ‘클래스 템플릿 인자 추론’ 가이드
- C++ 템플릿 인자 추론 | template argument deduction 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, Deduction Guides, CTAD, C++17, Templates 등으로 검색하시면 이 글이 도움이 됩니다.