Deduction Guides¶
Deduction guides are used for helping the compiler figure out what types go into the templates. Basically just rules on how to tie break collisions.
How to help the compiler help you¶
C++11 introduced the auto
keyword which helped folks save a lot of time and energy manually specifying types in some places. Doing something like:
double pi = 3.14159;
could now be accomplished with:
auto two_pi = 6.283185; // we can figure out auto should really just be double!
// Just to confirm they are indeed the same type:
static_assert(std::is_same_v<decltype(pi), decltype(two_pi)>);
While it seems trivial for this type, auto
really started to shine when we had crazier types involved. For example, when talking about iterators
:
#include <vector>
#include <iostream>
#include <memory>
// We used to have to do something like:
{
std::vector<std::pair<std::string, uint32_t>> people{std::make_pair("Kevin", 1), std::make_pair("Mike", 2), std::make_pair("Jenny", 3), std::make_pair("Laura", 4)};
std::vector<std::pair<std::string, uint32_t>>::iterator people_iter = people.begin(); // YUCK!!!
while (people_iter != people.end())
{
std::cout << people_iter->first << ", " << people_iter->second << std::endl;
++people_iter;
}
}
Kevin, 1
Mike, 2
Jenny, 3
Laura, 4
{
std::vector<std::pair<std::string, uint32_t>> people{ {"Kevin", 1}, {"Mike", 2}, {"Jenny", 3}, {"Laura", 4} };
auto people_iter = people.begin(); // No more dealing with the explicit types!
while (people_iter != people.end())
{
std::cout << people_iter->first << ", " << people_iter->second << std::endl;
++people_iter;
}
}
Kevin, 1
Mike, 2
Jenny, 3
Laura, 4
When auto
was introduced, it was a pretty big win when it came to readability. While overuse of anything is generally bad, auto
let us escape the grunt work of having the exact type manually written out. In the same spirit of avoiding the extra work of having to manually specify things, CTAD
was introduced. But before we talk about what it is and how it works, lets take a step back and talk about the world that existed before it.
Have you ever run across functions that look something like make_xxx
where xxx
was some type? These even exist in the standard library. A few common ones:
std::make_pair
std::make_tuple
std::make_unique
std::make_optional
…
This section is going to talk about why these were necessary, why they are no longer necessary starting C++17, and how you can also avoid using them (and more!).
So lets go through a simple example:
template <typename T, typename U>
struct MyPair
{
MyPair(const T& t, const U& u) {}
};
// In the pre c++17 days, if we wanted to create an instance of MyPair, we'd have to do something like:
MyPair<int, double> my_pair{1, 2.0};
// and what this meant is that you had to know and declare the types of something upfront.
// But this was not that convenient. There is enough information on the right hand side for us to determine what the types are
// but the compiler not being able to figure it is unfortunate. In order to help out the compiler, people started to introduce the
// make_xxx style methods that gave the compiler enough information to realize what the types of the templates should be!
template <typename T, typename U>
inline MyPair<T, U> make_my_pair(const T& t, const U& u)
{
return MyPair<T, U>(t, u);
}
// And then all was okay in the world and we could move on - we just had to use this make_xxx method now.
// So now people started writing:
auto my_other_pair = make_my_pair(1, 2.0);
While this is a simple example, you can take this a lot farther, and folks generally did. But this is a little cumbersome. It’s also not very efficient because now we are creating an extra function call and debugging this will be a pain. And why should we even have to define a seperate function anyway just so the constructor works out!
So C++17 introduced CTAD (Class Template Argument Deduction) that lets us gain a boost similar to the boost we gained when we had auto. Maybe we don’t have to explicitly specify all the types. We just need to nudge the compiler in the right direction with a guide!
So out went the old make_xxx
and in came… well, the ability to write less code. Starting C++17, we can instead do:
MyPair my_cool_pair{1, 2.0}; // and the compiler will figure out that it's really just MyPair<int, double>
static_assert(std::is_same_v<decltype(my_cool_pair), decltype(my_pair)>);
#include <type_traits>
std::cout << typeid(my_cool_pair).name() << std::endl;
std::cout << typeid(my_pair).name() << std::endl;
N11__cling_N566MyPairIidEE
N11__cling_N566MyPairIidEE
Wait a second, we didn’t actually do anything. Why does this work? It’s because in trivial cases, the deduction guides come for free. Just to show you that this didn’t work in C++14, here is a side by side comparison: C++14 vs C++17 auto deduction in trivial cases
If there are any special cases that the trivial version doesn’t capture, then we need to implement our own deduction guides. The standard library folks were nice enough to implement them for corner cases for things like std::pair
, std::tuple
, etc. If you want to check them out, see: Deduction guide for std::pair
So it’s cool that it comes for free in some cases and the standard library implemented some, but how do we write our own?
Writing your own deduction guide¶
Lets start with a example we can play around with:
Gotchas to watch out for¶
Caveats¶
Let’s start with the more obvious ones:
We can’t empty initialize something since the deduction guides depend on a type¶
std::pair<int, int> my_empty_pair{};
std::cout << my_empty_pair.first << std::endl;
0
In the above example, we just initialized an empty instance of the pair<int, int>
type and that was fine because the compiler had enough information (via our explicit expression) of what types are going into the pair. Once we start relying on the deduction guides, we can do:
std::pair my_auto_pair{1U, 2U};
std::cout << my_auto_pair.first << std::endl;
1
But no longer something like:
std::pair my_empty_pair_with_no_types{};
The compiler will rightfully puke on us with:
no viable constructor or deduction guide for deduction of template arguments of 'pair'
because there is no way for the compiler to know what we wanted in there! Heck, I don’t even know what I wanted in there.
All or nothing when it comes to specifying the types¶
Lets say we build a custom MyPair
type
template <typename A, typename B>
struct MyPair
{
MyPair(const A&, const B&) {};
};
MyPair one{1, 2.0}; // int, double
MyPair two{3, "4"}; // int, char
Out of luck when it comes to alias types¶
Pointer types can’t be deduced from a nullptr¶
Where they will fail¶
References:¶
If you want to continue learning more on this subject or are curious about other resources that might help in the learning process: