10.11 — Class template argument deduction (CTAD) and deduction guides

Class template argument deduction (CTAD) C++17

Starting in C++17, when instantiating an object from a class template, the compiler can deduce the template types from the types of the object’s initializer (this is called class template argument deduction or CTAD for short). For example:

#include <utility> // for std::pair

int main()
{
    std::pair<int, int> p1{ 1, 2 }; // explicitly specify class template std::pair<int, int> (C++11 onward)
    std::pair p2{ 1, 2 };           // CTAD used to deduce std::pair<int, int> from the initializers (C++17)

    return 0;
}

CTAD is only performed if no template argument list is present. Therefore, both of the following are errors:

#include <utility> // for std::pair

int main()
{
    std::pair<> p1 { 1, 2 };    // error: too few template arguments, both arguments not deduced
    std::pair<int> p2 { 3, 4 }; // error: too few template arguments, second argument not deduced

    return 0;
}

Author’s note

Many future lessons on this site make use of CTAD. If you’re compiling these examples using the C++14 standard, you’ll get an error about missing template arguments. You’ll need to explicitly add such arguments to the example to make it compile.

Template argument deduction guides C++17

In most cases, CTAD works right out of the box. However, in certain cases, the compiler may need a little extra help understanding how to deduce the template arguments properly.

You may be surprised to find that the following program (which is almost identical to the example that uses std::pair above) doesn’t compile in C++17:

// define our own pair type
template <typename T, typename U>
struct pair
{
    T first{};
    U second{};
};

int main()
{
    pair<int, int> p1{ 1, 2 }; // ok: we're explicitly specifying the template arguments
    pair p2{ 1, 2 };           // compile error in C++17

    return 0;
}

If you compile this in C++17, you’ll likely get some error about “class template argument deduction failed” or “cannot deduce template arguments” or “No viable constructor or deduction guide”. This is because in C++17, CTAD doesn’t know how to deduce the template arguments for aggregate class templates. To address this, we can provide the compiler with a deduction guide, which tells the compiler how to deduce the template arguments for a given class template.

Here’s the same program with a deduction guide:

template <typename T, typename U>
struct pair
{
    T first{};
    U second{};
};

// Here's a deduction guide for our pair class
// pair objects initialized with arguments of type T and U should deduce to pair<T, U>
template <typename T, typename U>
pair(T, U) -> pair<T, U>;
    
int main()
{
    pair<int, int> p1{ 1, 2 }; // explicitly specify class template pair<int, int> (C++11 onward)
    pair p2{ 1, 2 };     // CTAD used to deduce pair<int, int> from the initializers (C++17)

    return 0;
}

This example should compile under C++17.

The deduction guide for our pair class is pretty simple, but let’s take a closer look at how it works.

// Here's a deduction guide for our pair class
// pair objects initialized with arguments of type T and U should deduce to pair<T, U>
template <typename T, typename U>
pair(T, U) -> pair<T, U>;

First, we use the same template type definition as in our pair class. This makes sense, because if our deduction guide is going to tell the compiler how to deduce the types for a pair<T, U>, we have to define what T and U are (template types). Second, on the right hand side of the arrow, we have the type that we’re helping the compiler to deduce. In this case, we want the compiler to be able to deduce template arguments for objects of type pair<T, U>, so that’s exactly what we put here. Finally, on the left side of the arrow, we tell the compiler what kind of declaration to look for. In this case, we’re telling it to look for a declaration of some object named pair with two arguments (one of type T, the other of type U). We could also write this as pair(T t, U u) (where t and u are the names of the parameters, but since we don’t use t and u, we don’t need to give them names).

Putting it all together, we’re telling the compiler that if it sees a declaration of a pair with two arguments (of types T and U respectively), it should deduce the type to be a pair<T, U>.

So when the compiler sees the definition pair p2{ 1, 2 }; in our program, it will say, “oh, this is a declaration of a pair and there are two arguments of type int and int, so using the deduction guide, I should deduce this to be a pair<int, int>“.

Author’s note

A few notes about deduction guides.

First, std::pair (and other standard library template types) come with pre-defined deduction guides. This is why our example above that uses std::pair compiles fine in C++17 without us having to provide deduction guides ourselves.

Second, C++20 added the ability for the compiler to automatically generate deduction guides for aggregate class types, so the version of pair without the deduction guides should compile in C++20. This assumes your compiler supports feature P1816, which as of the time of writing, gcc and Visual Studio do, and Clang does not.

guest
Your email address will not be displayed
Avatars from https://gravatar.com/ are connected to your provided email address.
Notify me about replies:  
5 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments