无效
Version >= C++ 11
void_t
是一个元函数,它将任何(数量)类型映射到 void
类型。void_t
的主要目的是促进类型特征的写作。
std::void_t
将成为 C++ 17 的一部分,但在此之前,它实现起来非常简单:
template <class...> using void_t = void;
有些编译器需要稍微不同的实现:
template <class...>
struct make_void { using type = void; };
template <typename... T>
using void_t = typename make_void<T...>::type;
void_t
的主要应用是编写检查语句有效性的类型特征。例如,让我们检查一个类型是否具有不带参数的成员函数 foo()
:
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 {};
这是如何运作的?当我尝试实例化 has_foo<T>::value
时,这将导致编译器尝试寻找 has_foo<T, void>
的最佳专业化。我们有两个选项:主要和次要的选项,它们必须实例化底层表达式:
- 如果
T
确实有一个成员函数foo()
,那么返回的任何类型都会转换为void
,并且基于模板部分排序规则,专门化优先于主要类型。所以has_foo<T>::value
将是true
- 如果
T
没有这样的成员函数(或者它需要多个参数),那么对于特化而言替换失败,我们只有主要模板可以回退。因此,has_foo<T>::value
是false
。
一个更简单的案例:
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 {};
这不使用 std::declval
或 decltype
。
你可能会注意到 void 参数的常见模式。我们可以将其考虑在内:
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...>;
它隐藏了 std::void_t
的使用,并使 can_apply
像一个指示器,作为第一个模板参数提供的类型是否在将其他类型替换为其后形成良好。现在可以使用 can_apply
重写前面的示例:
template<class T>
using ref_t = T&;
template<class T>
using can_reference = can_apply<ref_t, T>; // Is T& well formed for T?
和:
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?
这似乎比原始版本更简单。
有关 std
特征的后 C++ 17 提案类似于 can_apply
。
void_t
的实用性是由 Walter Brown 发现的。他在 CppCon 2016 上做了精彩的演讲 。