`void_t`の仕組み


149

私はウォルターブラウンがCppcon14で最新のテンプレートプログラミング(パートIパートII)について語った彼のvoid_tSFINAEテクニックのプレゼンテーションを見ました。

例:すべてのテンプレート引数が正しい形式であるかどうか
を評価する単純な変数テンプレート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、それが機能するために使用されるものとまったく同じ型でなければならないと述べています。何故ですか?(このタイプが一致する必要がある理由がわかりません。デフォルトのタイプだけではうまくいきませんか?)


6
広告2)静的アサートが次のように記述されていると想像してくださいhas_member<A,int>::value。次に、評価される部分的な特殊化has_member<A,void>は一致しません。したがって、それはである必要がありますhas_member<A,void>::value。または、構文糖衣では、typeのデフォルト引数である必要がありますvoid
dyp '12

1
@dypありがとう、編集します。ええと、私はhas_member< T , class = void >デフォルトをする必要性をvoidまだ見ていません。このトレイトが常に1つのテンプレート引数でのみ使用されると仮定すると、デフォルトの引数はどのタイプでもかまいません。
センセーションなし

興味深い質問です。
AStopher 14

2
この提案では、open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4436.pdf、Walterがに変更されたtemplate <class, class = void>ことに注意してくださいtemplate <class, class = void_t<>>。そのため、void_tエイリアステンプレートの実装を使用して、私たちは何でも自由に実行できます:)
JohnKoch

回答:


133

1.プライマリクラステンプレート

を記述するhas_member<A>::valueと、コンパイラは名前has_memberを検索して、プライマリクラステンプレート、つまり次の宣言を見つけます。

template< class , class = void >
struct has_member;

(OPでは、それは定義として書かれています。)

テンプレート引数リスト<A>は、このプライマリテンプレートのテンプレートパラメータリストと比較されます。プライマリテンプレートには2つのパラメータがありますが、指定したのは1つだけなので、残りのパラメータはデフォルトでデフォルトのテンプレート引数に設定されます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<..>1つずつ照合します。最初に、テンプレート引数の演繹が実行されます。上記の部分的な特殊化は、引数で「埋める」必要があるテンプレートパラメータを持つテンプレートです。

最初のパターン Tは、コンパイラがtemplate-parameterを推定できるようにしますT。これは些細な控除ですが、のようなパターンを検討しT const&、我々はまだ推測することができ、T。パターンTとテンプレート引数についてはA、と推定TしますA

2番目のパターン void_t< decltype( T::member ) >では、テンプレートパラメータTは、テンプレート引数から推定できないコンテキストで表示されます。

これには2つの理由があります。

  • 内部の式decltypeは、テンプレート引数の控除から明示的に除外されています。これは、恣意的に複雑になる可能性があるためだと思います。

  • decltypelike なしのパターンを使用した場合でも、解決されたエイリアステンプレートでのvoid_t< T >差し引きが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>です。引数がスペシャライゼーションのパラメータと一致せず、プライマリテンプレートがフォールバックとして選択されています。


(*)標準、IMHOは混乱を招きますが、テンプレート引数演繹プロセスでは、置換プロセスと明示的に指定されたテンプレート引数のマッチングが含まれています。例(N4296以降)[temp.class.spec.match] / 2:

部分的な特殊化のテンプレート引数が実際のテンプレート引数リストから推定できる場合、部分的な特殊化は、指定された実際のテンプレート引数リストと一致します。

ただし、これは、部分的な特殊化のすべてのテンプレートパラメータを推定する必要があることを意味するだけではありません。これはまた、置換が成功しなければならないことを意味し、テンプレートの引数は、部分的な特殊化の(置換された)テンプレートパラメータと一致する必要があります。標準が置換された引数リストと提供された引数リストの間の比較を指定する場所を完全には認識していないことに注意してください。


3
ありがとうございました!私は何度も何度も読みましたが、テンプレート引数の控除が正確にどのように機能し、コンパイラが最終的なテンプレート用に選択するものは現時点では正しくないと思います。
センセーションなし2014

1
@ JohannesSchaub-litbありがとう!しかし、それは少し憂鬱です。テンプレート引数を特殊化と照合するための規則は本当にありませんか?明示的な専門分野でさえありませんか?
dyp、2014

2
W / r / tデフォルトテンプレート引数、open-std.org
TC

1
@dyp数週間後、これについてたくさん読んで、このスニペットのヒントから、これがどのように機能するかを理解し始めたと思います。ありがとうございます!
センセーションが無効

1
私が付け加えたいのは、プライマリテンプレートという用語が重要であること(テンプレートはコード内で最初に遭遇する)
意味のないものである

18
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };

上記の特殊化は、それが整形式である場合にのみ存在するため、when decltype( T::member )は有効であり、あいまいではありません。特化はhas_member<T , void>、コメントのとおりです。

あなたが書くときhas_member<A>、それはhas_member<A, void>デフォルトのテンプレート引数のためです。

そして、我々はのための専門を持っているhas_member<A, void>(soから継承true_type)が、我々はのための専門を持っていないhas_member<B, void>(:継承をから、我々は、デフォルトの定義を使用してfalse_type

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.