Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Automatic Template Argument Deduction
Let the compiler deduce template arguments from constructor parameters.
Class template argument deduction (CTAD) and deduction guides
Class template argument deduction (CTAD) C++17
Before C++17, when creating objects from class templates, you had to explicitly specify all template arguments:
#include <utility>
int main()
{
std::pair<double, int> measurement{ 98.6, 37 }; // temperature and reading count
return 0;
}
Starting in C++17, the compiler can deduce template types from initializers. This feature is called class template argument deduction (CTAD):
#include <utility>
int main()
{
std::pair measurement{ 98.6, 37 }; // CTAD deduces std::pair<double, int>
return 0;
}
The compiler examines the initializer types and determines the appropriate template arguments automatically.
CTAD requires no template argument list
CTAD only activates when you omit the template argument list entirely. Partial lists trigger errors:
#include <utility>
int main()
{
std::pair<> score{ 100, 95 }; // Error: empty list, not "no list"
std::pair<int> range{ 10, 20 }; // Error: partial list, second type not deduced
return 0;
}
Either provide all arguments explicitly or provide none and let CTAD handle it.
Literal suffixes affect deduction
Since CTAD examines initializer types, literal suffixes change what gets deduced:
#include <utility>
int main()
{
std::pair sizes1{ 640, 480 }; // pair<int, int>
std::pair sizes2{ 640u, 480u }; // pair<unsigned int, unsigned int>
std::pair sizes3{ 640.0, 480.0 }; // pair<double, double>
std::pair sizes4{ 640.0f, 480.0f }; // pair<float, float>
return 0;
}
Deduction guides for custom templates
Standard library templates like std::pair include built-in deduction guides. For your own aggregate templates, C++17 requires explicit guides:
template <typename T, typename U>
struct Result
{
T value{};
U errorCode{};
};
// Deduction guide: Result initialized with types T and U becomes Result<T, U>
template <typename T, typename U>
Result(T, U) -> Result<T, U>;
int main()
{
Result<double, int> r1{ 3.14, 0 }; // Explicit - always works
Result r2{ 3.14, 0 }; // CTAD - requires deduction guide in C++17
return 0;
}
The deduction guide tells the compiler how to map initialization patterns to template arguments.
Understanding deduction guide syntax
template <typename T, typename U>
Result(T, U) -> Result<T, U>;
Breaking this down:
template <typename T, typename U>declares the template parametersResult(T, U)describes the pattern to match (a Result with two arguments)-> Result<T, U>specifies what type to deduce
When the compiler sees Result r{ 3.14, 0 }, it matches Result(double, int) to the guide and deduces Result<double, int>.
Single-type templates
For templates with one type parameter used multiple times:
template <typename T>
struct Bounds
{
T lower{};
T upper{};
};
template <typename T>
Bounds(T, T) -> Bounds<T>;
int main()
{
Bounds range{ 0, 100 }; // Bounds<int>
Bounds limits{ 0.0, 1.0 }; // Bounds<double>
return 0;
}
C++20 automatically generates deduction guides for aggregates, making explicit guides unnecessary for simple cases. The examples above work without guides in C++20 and later.
Standard library types like std::pair include pre-defined guides, which is why they work with CTAD immediately.
Default template arguments
Template parameters can have default values:
template <typename T = int, typename U = int>
struct Entry
{
T key{};
U data{};
};
template <typename T, typename U>
Entry(T, U) -> Entry<T, U>;
int main()
{
Entry<double, std::string> item1{ 1.5, "active" }; // Explicit
Entry item2{ 2.5, "pending" }; // CTAD: Entry<double, const char*>
Entry item3{}; // Defaults: Entry<int, int>
return 0;
}
When neither explicit arguments nor initializers provide type information, defaults apply.
CTAD limitations
Non-static member initialization
CTAD cannot deduce types in member declarations:
#include <utility>
struct SensorLog
{
std::pair<double, int> reading1{ 98.6, 1 }; // OK - explicit types
std::pair reading2{ 98.6, 1 }; // Error - CTAD not allowed here
};
int main()
{
std::pair reading{ 98.6, 1 }; // OK - CTAD works in function scope
return 0;
}
Always specify template arguments explicitly for class member variables.
Function parameters
CTAD works on template arguments, not parameters. Function parameter types cannot use CTAD:
#include <iostream>
#include <utility>
void logReading(std::pair data) // Error: can't deduce parameter type
{
std::cout << data.first << '\n';
}
int main()
{
std::pair reading{ 98.6, 1 };
logReading(reading);
return 0;
}
Use function templates instead:
#include <iostream>
#include <utility>
template <typename T, typename U>
void logReading(std::pair<T, U> data)
{
std::cout << data.first << '\n';
}
int main()
{
std::pair reading{ 98.6, 1 }; // CTAD deduces pair<double, int>
logReading(reading); // Template deduces T=double, U=int
return 0;
}
Summary
Class template argument deduction (CTAD) lets the compiler determine template arguments from initializers, reducing verbosity when creating template objects.
CTAD activation: Omit the entire template argument list. Empty <> or partial lists cause errors.
Deduction guides map initialization patterns to template instantiations. C++17 requires them for user-defined aggregates; C++20 generates them automatically.
Default template arguments provide fallback types when neither explicit arguments nor initializers supply type information.
Limitations: CTAD cannot be used with non-static member initializers or function parameter declarations.
CTAD simplifies generic code by eliminating redundant type specifications while maintaining full type safety.
Automatic Template Argument Deduction - Quiz
Test your understanding of the lesson.
Practice Exercises
CTAD with Custom Container
Create a class template that uses CTAD to deduce template arguments from initializers. Demonstrate both explicit template arguments and CTAD.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!