C++17coreadvanced
Class Template Argument Deduction (CTAD)
C++17 · Practical Modern · Compiler flag: -std=c++17
Explanation
CTAD extends template argument deduction for class templates with user-defined deduction guides, allowing library authors to control how template parameters are inferred from constructor calls. This is the mechanism that makes deduction guides possible.
Real-World Scenario
Write a custom wrapper class that automatically deduces its template parameter from the constructor argument, simplifying client code in a plugin system.
Quick Snippet
snippet.cpp
template<typename T>
struct Wrapper {
T value;
Wrapper(T v) : value(v) {}
};
// Deduction guide: Wrapper constructed from const char* deduces as Wrapper<std::string>
Wrapper(const char*) -> Wrapper<std::string>;
Wrapper w1(42); // Wrapper<int>
Wrapper w2("hello"); // Wrapper<std::string> via deduction guideFull Example
ctad.cpp
// CodeNebula - C++17 Example: CTAD with Deduction Guides
// Compile: g++ -std=c++17 -Wall -Wextra -o example ctad.cpp
// Demonstrates user-defined deduction guides for class template argument
// deduction. Deduction guides tell the compiler how to deduce template
// parameters from constructor arguments when implicit deduction is insufficient.
#include <iostream>
#include <string>
#include <vector>
#include <iterator>
// ------------------------------------------------------------------
// 1. Simple wrapper with implicit deduction (no guide needed)
// ------------------------------------------------------------------
template <typename T>
struct Wrapper {
T value;
Wrapper(T v) : value(v) {}
};
// Implicit guide: Wrapper(T) -> Wrapper<T> is generated automatically
// ------------------------------------------------------------------
// 2. Custom deduction guide: const char* -> std::string
// ------------------------------------------------------------------
template <typename T>
struct Named {
std::string label;
T data;
Named(const char* l, T d) : label(l), data(d) {}
};
// Without a guide, Named("hello", 42) deduces T from 42 but label is const char*
// This explicit guide ensures the deduction works correctly:
template <typename T>
Named(const char*, T) -> Named<T>;
// ------------------------------------------------------------------
// 3. Deduction guide to pick a different specialization
// ------------------------------------------------------------------
template <typename T>
struct Container {
std::vector<T> data;
Container(std::initializer_list<T> init) : data(init) {}
// Constructor from iterator pair
template <typename Iter>
Container(Iter begin, Iter end) : data(begin, end) {}
void print() const {
std::cout << " [";
for (std::size_t i = 0; i < data.size(); ++i) {
if (i > 0) std::cout << ", ";
std::cout << data[i];
}
std::cout << "]" << std::endl;
}
};
// Deduction guide for iterator pair: deduce T from iterator's value_type
template <typename Iter>
Container(Iter, Iter) -> Container<typename std::iterator_traits<Iter>::value_type>;
// ------------------------------------------------------------------
// 4. Deduction guide that transforms types
// ------------------------------------------------------------------
template <typename T>
struct SmartValue {
T val;
explicit SmartValue(T v) : val(v) {}
};
// Guide: when constructed from const char*, deduce as std::string
SmartValue(const char*) -> SmartValue<std::string>;
int main()
{
// 1. Implicit deduction
std::cout << "=== implicit deduction ===" << std::endl;
Wrapper w1{42}; // Wrapper<int>
Wrapper w2{3.14}; // Wrapper<double>
Wrapper w3{std::string("hello")}; // Wrapper<std::string>
std::cout << " w1: " << w1.value << std::endl;
std::cout << " w2: " << w2.value << std::endl;
std::cout << " w3: " << w3.value << std::endl;
// 2. Custom deduction guide
std::cout << "\n=== custom deduction guide ===" << std::endl;
Named n("sensor", 42); // Named<int> via deduction guide
std::cout << " label: " << n.label << ", data: " << n.data << std::endl;
// 3. Iterator deduction guide
std::cout << "\n=== iterator deduction guide ===" << std::endl;
std::vector<double> src{1.1, 2.2, 3.3, 4.4};
Container c1{src.begin(), src.end()}; // Container<double>
std::cout << " from iterators:";
c1.print();
Container c2{10, 20, 30}; // Container<int> from init-list
std::cout << " from init-list:";
c2.print();
// 4. Type-transforming deduction guide
std::cout << "\n=== type-transforming guide ===" << std::endl;
SmartValue sv1{42}; // SmartValue<int>
SmartValue sv2{"hello"}; // SmartValue<std::string> via guide
std::cout << " sv1: " << sv1.val << std::endl;
std::cout << " sv2: " << sv2.val << std::endl;
return 0;
}
/*
Expected output:
=== implicit deduction ===
w1: 42
w2: 3.14
w3: hello
=== custom deduction guide ===
label: sensor, data: 42
=== iterator deduction guide ===
from iterators: [1.1, 2.2, 3.3, 4.4]
from init-list: [10, 20, 30]
=== type-transforming guide ===
sv1: 42
sv2: hello
*/
Run command
g++ -std=c++17 -Wall -Wextra -o example ctad.cpp