web-dev-qa-db-ja.com

`void_t`の仕組み

Walter BrownのCppcon14での最新のテンプレートプログラミング( Part IPart 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 >_したがって、このテンプレートは破棄されます
  • コンパイラは、デフォルト引数としてvoidを使用して_has_member< B , class = void >_を検出します
  • _has_member< B >_は_false_type_に評価されます

http://ideone.com/HCTlBb

質問:
1。これについての私の理解は正しいですか?
2。 Walter Brownは、デフォルトの引数は_void_t_で使用されるものとまったく同じ型でなければならないことを述べています。何故ですか? (このタイプが一致する必要がある理由はわかりませんが、デフォルトのタイプだけが仕事をするのではありませんか?)

137
nonsensation

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について、TAであると推定します。

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は、置換された引数リストと提供された引数リストの比較を標準で指定していることを完全には認識していないことに注意してください。

119
dyp
// 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から継承します)

17
Jarod42