Walter BrownのCppcon14での最新のテンプレートプログラミング( Part I 、 Part II )に関する講演を見て、そこで彼は_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 >
_を検出しますhas_member< B >
_は_false_type
_に評価されます質問:
1。これについての私の理解は正しいですか?
2。 Walter Brownは、デフォルトの引数は_void_t
_で使用されるものとまったく同じ型でなければならないことを述べています。何故ですか? (このタイプが一致する必要がある理由はわかりませんが、デフォルトのタイプだけが仕事をするのではありませんか?)
has_member<A>::value
を記述すると、コンパイラはhas_member
という名前を検索し、primaryクラステンプレート、つまり次の宣言を見つけます。
template< class , class = void >
struct has_member;
(OPでは、それは定義として書かれています。)
テンプレート引数リスト<A>
は、このプライマリテンプレートのテンプレートパラメータリストと比較されます。プライマリテンプレートには2つのパラメーターがありますが、指定したパラメーターは1つだけなので、残りのパラメーターはデフォルトのテンプレート引数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<..>
に1つずつ一致させようとします。最初に、テンプレート引数の推論が実行されます。上記の部分的な特殊化は、引数で「入力」する必要があるテンプレートパラメータを持つテンプレートです。
最初のパターンT
を使用すると、コンパイラーはテンプレートパラメーターT
を推測できます。これは簡単な推論ですが、T const&
のようなパターンを考えてみてください。このパターンでは、T
を推論できます。パターンT
およびテンプレート引数A
について、T
はA
であると推定します。
2番目のパターンvoid_t< decltype( T::member ) >
では、テンプレートパラメータT
は、テンプレート引数から推測できないコンテキストに表示されます。これには2つの理由があります。
decltype
内の式は、テンプレート引数の推論から明示的に除外されます。これは、arbitrarily意的に複雑になる可能性があるためだと思います。
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 ) > >
を評価できるようになりました。置換後は整形式であるため、Substitution Failureは発生しません。我々が得る:
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:
部分的な特殊化のテンプレート引数が実際のテンプレート引数リストから推定できる場合、部分的な特殊化は特定の実際のテンプレート引数リストと一致します。
しかし、これはjustではなく、部分的な特殊化のすべてのテンプレートパラメータを推測する必要があることを意味します。また、置換は成功する必要があり、テンプレート引数は部分特殊化の(置換された)テンプレートパラメーターと一致する必要があることを意味します。 whereは、置換された引数リストと提供された引数リストの比較を標準で指定していることを完全には認識していないことに注意してください。
// 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
から継承します)