Card 1 / 7
Before C++17, selecting behaviour on a type property meant two helper overloads and an empty tag object to pick between them — the pattern is in the file for comparison. if constexpr collapses all of it into one function.
value |
T |
weight(value) |
branch taken |
|---|---|---|---|
5 |
int |
5 |
integral |
0 |
int |
0 |
integral |
std::string("abc") |
std::string |
3 |
size() |
std::vector<int>{1, 2} |
std::vector<int> |
2 |
size() |
if constexpr (std::is_integral_v<T>) with a matching else. The branch not taken is discarded for that instantiation, so value.size() never has to be valid for an int — which is precisely why tag dispatch existed.
What the older pattern bought was the same discarding, at the cost of two extra functions and a std::is_integral<T>{} object constructed only to be overload-resolved on and thrown away.
// Write the same selection with if constexpr: one function, no helper
// overloads, no tag objects.
//
// integral T -> the value itself, converted to std::size_t
// otherwise -> value.size()
//
// The branch not taken is DISCARDED, so value.size() never has to make sense
// for an int.
template <class T>
std::size_t weight(const T& value) {
if constexpr (std::is_integral_v<T>) {
return static_cast<std::size_t>(value);
} else {
return value.size();
}
}Card 2 / 7
In the curiously recurring template pattern (CRTP) a base class takes the derived type as its template parameter, so it can cast *this down and call derived members with no virtual dispatch. The catch: the mixin's members are declared…