C++ void_t


Example

C++11

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:

  • If 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
  • If 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.