簡體   English   中英

`void_t` 是如何工作的

[英]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

http://ideone.com/HCTlBb

問題:
1.我的理解正確嗎?
2. Walter Brown 聲明默認參數必須與void_t中使用的類型完全相同才能正常工作。 這是為什么? (我不明白為什么這些類型需要匹配,不只是任何默認類型都可以完成這項工作嗎?)

1.小學班級模板

當您編寫has_member<A>::value時,編譯器會查找名稱has_member並找到類模板,即此聲明:

template< class , class = void >
struct has_member;

(在 OP 中,這是作為定義編寫的。)

模板參數列表<A>與此主模板的模板參數列表進行比較。 由於主模板有兩個參數,但您只提供了一個,剩余參數默認為默認模板參數: void 就好像你寫has_member<A, void>::value

2. 專業類模板

現在,將模板參數列表與模板has_member的任何特化進行比較。 僅當沒有專業化匹配時,主模板的定義才用作后備。 所以考慮了偏特化:

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };

編譯器嘗試將模板參數A, void與部分特化中定義的模式: Tvoid_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
{ };

3. 選擇

現在,我們可以將此特化的模板參數列表與提供給原始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;

標准實施

這個 std::void_t 參考

#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;

情況1

#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>

案例2

#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;

這個案例表明編譯器:

  1. 想要找到匹配has_type_member<float>
  2. 發現模板需要 2 個參數,然后用默認參數填充第二個參數。 該結構就像has_type_member<float, void>
  3. 找到了這個簽名的一個特化,並從std::true_type得到了值

案例3

#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;

案例 f

  1. has_type_member<float>已完成為has_type_member<float, void>
  2. 然后編譯器嘗試了typename float::type並失敗了。
  3. 選擇的主要模板。

案例一

  1. has_type_member<A>已完成為has_type_member<A, void>
  2. 然后編譯器嘗試has_type_member<A, typename A::type>並發現它是has_type_member<A, int>
  3. 編譯器認為它不是has_type_member<A, void>的特殊化
  4. 然后選擇主要模板。

案例b

  1. has_type_member<B>已完成為has_type_member<B, void>
  2. 然后編譯器嘗試has_type_member<B, typename B::type>並發現它是has_type_member<B, void>
  3. 編譯器認為它是has_type_member<B, void>的一種特殊化
  4. 選擇了true_type

案例4

#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

  1. 檢查T::type是否有效。
  2. 如果僅提供一個模板參數,則提供主模板的特化。

是正確的? 不需要 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM