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: