[英]How does `void_t` work
我在 Cppcon14 上觀看了 Walter Brown 關於現代模板編程的演講(第一部分,第二部分),他展示了他的void_t
SFINAE 技術。
例子:
給定一個簡單的變量模板,如果所有模板參數格式正確,則計算結果為void
:
template< class ... > using void_t = void;
以及檢查是否存在名為member的成員變量的以下特征:
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
我試圖理解這是為什么以及如何工作的。 因此,一個小例子:
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1. has_member< A >
has_member< A , void_t< decltype( A::member ) > >
A::member
存在decltype( A::member )
格式正確void_t<>
有效並計算為void
has_member< A , void >
因此它選擇了專門的模板has_member< T , void >
並計算為true_type
2. has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::member
不存在decltype( B::member )
格式不正確並且靜默失敗 (sfinae)has_member< B , expression-sfinae >
所以這個模板被丟棄has_member< B , class = void >
以 void 作為默認參數has_member< B >
計算結果為false_type
問題:
1.我的理解正確嗎?
2. Walter Brown 聲明默認參數必須與void_t
中使用的類型完全相同才能正常工作。 這是為什么? (我不明白為什么這些類型需要匹配,不只是任何默認類型都可以完成這項工作嗎?)
當您編寫has_member<A>::value
時,編譯器會查找名稱has_member
並找到主類模板,即此聲明:
template< class , class = void >
struct has_member;
(在 OP 中,這是作為定義編寫的。)
模板參數列表<A>
與此主模板的模板參數列表進行比較。 由於主模板有兩個參數,但您只提供了一個,剩余參數默認為默認模板參數: void
。 就好像你寫has_member<A, void>::value
。
現在,將模板參數列表與模板has_member
的任何特化進行比較。 僅當沒有專業化匹配時,主模板的定義才用作后備。 所以考慮了偏特化:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
編譯器嘗試將模板參數A, void
與部分特化中定義的模式: T
和void_t<..>
一一匹配。 首先,執行模板參數推導。 上面的部分特化仍然是一個帶有模板參數的模板,需要用參數“填充”。
第一個模式T
允許編譯器推導出模板參數T
。 這是一個微不足道的推論,但考慮像T const&
這樣的模式,我們仍然可以推導出T
。 對於模式T
和模板參數A
,我們將T
推斷為A
。
在第二個模式void_t< decltype( T::member ) >
中,模板參數T
出現在不能從任何模板參數推導出的上下文中。
有兩個原因:
decltype
中的表達式被顯式排除在模板參數推導之外。 我想這是因為它可以任意復雜。即使我們使用了像
void_t< T >
這樣沒有decltype
的模式,那么T
的推導也會發生在解析的別名模板上。 也就是說,我們解析別名模板,然后嘗試從結果模式中推斷出類型T
然而,生成的模式是void
,它不依賴於T
,因此不允許我們找到T
的特定類型。 這類似於試圖反轉一個常數函數的數學問題(在這些術語的數學意義上)。
模板參數推導完成(*) ,現在替換推導的模板參數。 這將創建一個如下所示的專業化:
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
現在可以評估類型void_t< decltype( A::member ) >
。 它在替換后是良構的,因此不會發生替換失敗。 我們得到:
template<>
struct has_member<A, void> : true_type
{ };
現在,我們可以將此特化的模板參數列表與提供給原始has_member<A>::value
的模板參數進行比較。 兩種類型都完全匹配,因此選擇了這種部分特化。
另一方面,當我們將模板定義為:
template< class , class = int > // <-- int here instead of void
struct has_member : false_type
{ };
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
我們最終得到了相同的專業化:
template<>
struct has_member<A, void> : true_type
{ };
但是我們的has_member<A>::value
的模板參數列表現在是<A, int>
。 參數與特化的參數不匹配,並且選擇主模板作為后備。
(*)恕我直言,該標准令人困惑,包括替換過程和模板參數推導過程中顯式指定模板參數的匹配。 例如(N4296 后)[temp.class.spec.match]/2:
如果可以從實際模板參數列表中推導出部分特化的模板參數,則部分特化匹配給定的實際模板參數列表。
但這不僅僅意味着必須推導出偏特化的所有模板參數; 這也意味着替換必須成功並且(看起來?)模板參數必須匹配部分特化的(替換的)模板參數。 請注意,我並不完全了解標准在何處指定替換參數列表和提供的參數列表之間的比較。
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };
上述特化僅在格式正確時才存在,因此當decltype( T::member )
有效且不模棱兩可時。 has_member<T , void>
的專業化是評論中的狀態。
當您編寫has_member<A>
時,它是has_member<A, void>
因為默認模板參數。
我們對has_member<A, void>
有特化(所以從true_type
繼承)但我們對has_member<B, void>
沒有特化(所以我們使用默認定義:從false_type
繼承)
這個線程和線程SFINAE:Understanding void_t and detect_if救了我。 我想通過一些例子來展示這種行為:
工具: cppinsights
通過浮點類型和以下類型測試實現:
struct A {
using type = int;
};
struct B{
using type = void;
}
測試者
auto f = has_type_member<float>::value;
auto a = has_type_member<A>::value;
auto b = has_type_member<B>::value;
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
輸出
bool f = false;
bool a = true;
bool b = true;
bool x = has_type_member<A, int>::value; //x = false;
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
template< class T >
struct has_type_member<T, void> : std::true_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
輸出
/home/insights/insights.cpp:14:8: error: redefinition of 'has_type_member<T, std::void_t<typename T::type>>'
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/insights/insights.cpp:8:8: note: previous definition is here
struct has_type_member<T, void>: std::true_type {};
^
1 error generated.
Error while processing /home/insights/insights.cpp.
因此, has_type_member<T, std::void_t<typename T::type>>
定義了has_type_member
的特化,並且簽名正是has_type_member<T, void>
。
#include <type_traits>
template< class, class = void >
struct has_type_member : std::false_type { };
// specialize 2nd type as void
template< class T>
struct has_type_member<T, void> : std::true_type { };
輸出:
bool f = true;
bool a = true;
bool b = true;
這個案例表明編譯器:
has_type_member<float>
has_type_member<float, void>
std::true_type
得到了值#include <type_traits>
template< class, class = void >
struct has_type_member : std::false_type { };
template<class T>
struct has_type_member<T, typename T::type>: std::true_type {};
輸出:
bool f = false;
bool a = false;
bool b = true;
has_type_member<float>
已完成為has_type_member<float, void>
。typename float::type
並失敗了。has_type_member<A>
已完成為has_type_member<A, void>
has_type_member<A, typename A::type>
並發現它是has_type_member<A, int>
has_type_member<A, void>
的特殊化has_type_member<B>
已完成為has_type_member<B, void>
。has_type_member<B, typename B::type>
並發現它是has_type_member<B, void>
。has_type_member<B, void>
的一種特殊化true_type
。#include <type_traits>
//int as default 2nd argument
template< class, class = int >
struct has_type_member : std::false_type { };
template<class T>
struct has_type_member<T, std::void<typename T::type>>: std::true_type {};
輸出:
bool f = false;
bool a = false;
bool b = false;
has_type_member<T>
是所有 3 個變量的has_type_member<T, int>
類型,而true_type
的簽名為has_type_member<T, void>
如果它是有效的。
所以, std::void_t
:
T::type
是否有效。是正確的? 不需要 std::void_t
模板<typename T, typename = T> struct isDefaultConstruct : std::false_type{};
模板結構 isDefaultConstruct<T, decltype(T())> : std::true_type{};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.