...

/

Class Template Argument Deduction

Class Template Argument Deduction

Develop a thorough understanding of class template argument deduction.

Instantiating a class template before C++17

Before C++17, template argument deduction only worked for functions but not classes. This meant that when a class template had to be instantiated, all the template arguments had to be supplied. The following snippet shows several examples:

Press + to interact
template<typename T>
struct wrapper
{
T data;
};
std::pair<int, double> p{ 42, 42.0 };
std::vector<int> v{ 1,2,3,4,5 };
wrapper<int> w{ 42 };

By leveraging template argument deduction for function templates, some standard types feature helper functions that create an instance of the type without the need to explicitly specify template arguments. Such examples are std::make_pair for std::pair and std::make_unique for std::unique_ptr. These helper function templates, used in corroboration with the auto keyword, avoid the need for specifying template arguments for class templates. Here’s an example:

Press + to interact
auto p = std::make_pair(42, 42.0);

Although not all standard class templates have such a helper function for creating instances, it’s not hard to write our own. In the following snippet, we can see a make_vector function template used to create a std::vector<T> instance and a make_wrapper function template to create a wrapper<T> instance:

Press + to interact
template<typename T, typename... Ts, typename Allocator = std::allocator<T>>
auto make_vector(T&& first, Ts&&... args)
{
return std::vector<std::decay_t<T>, Allocator> {
std::forward<T>(first),
std::forward<Ts>(args)...
};
}
template<typename T>
constexpr wrapper<T> make_wrapper(T&& data)
{
return wrapper<T> { data }; // Template argument `<T>` is added by the TCE
// as this syntax was added in C++17 and above
}
auto v = make_vector(1, 2, 3, 4, 5);
auto w = make_wrapper(42);

Class template argument deduction after C++17

The C++17 standard has simplified the use of class templates by providing template argument deduction for them. Therefore, as of C++17, the first snippet shown in this lesson can be simplified as follows:

Press + to interact
std::pair p{ 42, 42.0 }; // std::pair<int, double>
std::vector v{ 1,2,3,4,5 }; // std::vector<int>
wrapper w{ 42 }; // wrapper<int>

This is possible because the compiler is able to deduce the template arguments from the type of the initializers. In this example, the compiler deduced it from the initializing expression of the variables. But the compiler is also able to deduce template arguments from new expressions and function-style cast expressions. These are exemplified next:

Press + to interact
template<typename T>
struct point_t
{
point_t(T vx, T vy) : x(vx), y(vy) {}
private:
T x;
T y;
};
auto p = new point_t(1, 2); // [1] point<int>
// new expression
std::mutex mt;
auto l = std::lock_guard(mt); // [2] std::lock_guard<std::mutex>
// function-style cast expression

The way template argument deduction works for class templates is different than for function templates, but it relies on the latter. When encountering the name of a class template in a ...