無效
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 上做了精彩的演講 。