I recently discovered an interesting feature: void_t.

void_t is a new feature introduced in C++17, and its definition is simple (some compiler implementations may not be, but they are broadly similar).

1
2
template< class... >
using void_t = void;

It looks simple, but with SFINAE it can be very useful in template metaprogramming.

For example, determining at compile time whether a class has a certain type using.

1
2
3
4
5
6

template <class, class = std::void_t<>>
struct has_type : std::false_type {};

template <class T>
struct has_type<T, std::void_t<typename T::type>> : std::true_type {};

For example, to determine if a member is present.

1
2
3
4
5
template <class, class = std::void_t<>>
struct has_a_member : std::false_type {};

template <class T>
struct has_a_member<T, std::void_t<decltype(std::declval<T>().a)>> : std::true_type {};

For example, to determine whether a class is iterable.

1
2
3
4
5
template <typename, typename = void>
constexpr bool is_iterable{};

template <typename T>
constexpr bool is_iterable<T, std::void_t<decltype(std::declval<T>().begin()), decltype(std::declval<T>().end())>> = true;

For example, to determine whether a class has a certain function.

1
2
3
4
5
template <class T, class = void>
struct has_hello_func : std::false_type {};

template <class T>
struct has_hello_func<T, std::void_t<decltype(std::declval<T>().hello())>> : std::true_type {};

The test results are as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

struct HasType {
  typedef int type;
};
struct NHasType {
  int hello;
};

struct Hasa {
  int a;
};
struct NHasa {
  int b;
};

struct HasHello {
  void hello();
};
struct NoHasHello {};

int main() {
  std::cout << has_type<HasType>::value << '\n';   // 1
  std::cout << has_type<NHasType>::value << '\n';  // 0

  std::cout << has_a_member<Hasa>::value << '\n';   // 1
  std::cout << has_a_member<NHasa>::value << '\n';  // 0

  std::cout << has_hello_func<HasHello>::value << '\n';    // 1
  std::cout << has_hello_func<NoHasHello>::value << '\n';  // 0

  std::cout << is_iterable<std::vector<double>> << '\n';  // 1
  std::cout << is_iterable<double> << '\n';               // 0
}

Its principle is actually the use of SFINAE and template priority to find the specialization to match the characteristics, you should be able to understand by looking directly at the sample code.