void_t
is a meta-function that maps any (number of) types to type void
.
The primary purpose of void_t
is to facilitate writing of type traits.
std::void_t
will be part of C++17, but until then, it is extremely straightforward to implement:
template <class...> using void_t = void;
Some compilers require a slightly different implementation:
template <class...>
struct make_void { using type = void; };
template <typename... T>
using void_t = typename make_void<T...>::type;
The primary application of void_t
is writing type traits that check validity of a statement. For example, let's check if a type has a member function foo()
that takes no arguments:
template <class T, class=void>
struct has_foo : std::false_type {};
template <class T>
struct has_foo<T, void_t<decltype(std::declval<T&>().foo())>> : std::true_type {};
How does this work? When I try to instantiate has_foo<T>::value
, that will cause the compiler to try to look for the best specialization for has_foo<T, void>
. We have two options: the primary, and this secondary one which involves having to instantiate that underlying expression:
T
does have a member function foo()
, then whatever type that returns gets converted to void
, and the specialization is preferred to the primary based on the template partial ordering rules. So has_foo<T>::value
will be true
T
doesn't have such a member function (or it requires more than one argument), then substitution fails for the specialization and we only have the primary template to fallback on. Hence, has_foo<T>::value
is false
.A simpler case:
template<class T, class=void>
struct can_reference : std::false_type {};
template<class T>
struct can_reference<T, std::void_t<T&>> : std::true_type {};
this doesn't use std::declval
or decltype
.
You may notice a common pattern of a void argument. We can factor this out:
struct details {
template<template<class...>class Z, class=void, class...Ts>
struct can_apply:
std::false_type
{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:
std::true_type
{};
};
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
which hides the use of std::void_t
and makes can_apply
act like an indicator whether the type supplied as the first template argument is well-formed after substituting the other types into it. The previous examples may now be rewritten using can_apply
as:
template<class T>
using ref_t = T&;
template<class T>
using can_reference = can_apply<ref_t, T>; // Is T& well formed for T?
and:
template<class T>
using dot_foo_r = decltype(std::declval<T&>().foo());
template<class T>
using can_dot_foo = can_apply< dot_foo_r, T >; // Is T.foo() well formed for T?
which seems simpler than the original versions.
There are post-C++17 proposals for std
traits similar to can_apply
.
The utility of void_t
was discovered by Walter Brown. He gave a wonderful presentation on it at CppCon 2016.