Type traits
Type traits in conjunction with enable_if
are turning into a required foundation for writing
template classes. Consider std::filesystem::path
: this has overloads that require help in order
for the compiler to disambiguate some of the required constructors and operators.
Let’s start with an interesting base, integral_constant
. This turns a constant into a type,
which means that templates can use it to control which template gets instantiated.
A modern version might look like this:
template<class T, T val>
struct integral_constant
{
static constexpr T value = val;
using value_type = T;
using type = integral_constant;
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; }
};
template <bool B> using bool_constant = integral_constant<bool, B>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
The bool_constant
specialization is provided as of C++17. However, except for the modern syntax
(e.g. using
instead of typedef
), this template is basic C++.
The entire reason for this is the side effects; otherwise, it’s a very expensive (in lines of code) way to represent a constant.
Starting with the most simple usage, we have two unique types true_type
and false_type
that are
specializations of bool_constant
, itself a specialization of integral_constant
. We can do
tag dispatching to select between two options at compile time. Let’s say we have a print function and
we want integer and floating point versions
template<typename T>
void print_impl(T val, true_type)
{
printf("%d", val);
}
template<typename T>
void print_impl(T val, false_type)
{
printf("%f", val);
}
template<typename T>
void print(T val)
{
print_impl(val, is_integral<T>());
}
Here, is_integral
is a type trait that inherits from bool_constant
and returns true_type
if
we have a type that is integral, e.g. int
, short
, etc. We could use normal overloading, but the promotion
rules often defeat our attempts at overloading.
Of course, as you see, we almost never use true_type
and false_type
directly. Instead, they are the
base type for type traits.
void_t
C++17 has void_t
:
and this is simply:
template< class... >
using void_t = void;
with the example being
template <typename T, typename = void>
struct is_iterable : std::false_type {};
template <typename T>
struct is_iterable<T, std::void_t<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>>
: std::true_type {};
where is_iterable
is a type trait and has a true
value if type T
is an iterable type, determined
by the fact that
An earlier version was proposed in: