1. ホーム
  2. c++

[解決済み] void_t` の仕組み

2022-04-23 16:53:44

質問

Cppcon14でWalter Brownのモダンなテンプレートプログラミングについての講演を見ました( 第一部 , 第二部 を発表しました。 void_t SFINAEテクニック



と評価される単純な変数テンプレートがあるとします。 void もし全てのテンプレート引数が正しく形成されていれば

template< class ... > using void_t = void;

というメンバ変数が存在するかどうかをチェックする次のようなtraitがあります。 メンバー :

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. ウォルター・ブラウンは、デフォルトの引数は、全く同じ型でなければならないと述べています。 void_t を使用することができます。なぜでしょうか?(なぜこの型が一致する必要があるのかわかりません。デフォルトの型なら何でもいいのではないでしょうか?)

どのように解決するのですか?

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<..> を一つずつ 最初 の場合、テンプレート引数の演繹が行われます。上記の部分的な特殊化は、引数によって "filled"される必要があるテンプレートパラメータを持つテンプレートであることに変わりはありません。

最初のパターン T というテンプレート・パラメータを推測することができます。 T . これは些細な推論ですが、次のようなパターンを考えてみてください。 T const& を推論することができます。 T . パターン T とテンプレート引数 A とすると T である。 A .

2つ目のパターンでは void_t< decltype( T::member ) > の場合、テンプレート・パラメータは T は、どのテンプレート引数からも推論できない文脈で出現します。

<ブロッククオート

その理由は2つあります。

  • の中の式は decltype は明示的にテンプレート引数控除から除外されます。これは恣意的に複雑になりうるからだろう。

  • のないパターンを使ったとしても decltype のように 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が混乱させるように、置換処理と明示的に指定されたテンプレート引数のマッチングを テンプレート引数控除 の処理を行う。例えば、(post-N4296) [temp.class.spec.match]/2:

部分的な特殊化は、与えられた実際のテンプレート引数リストにマッチします。 部分的な特殊化のテンプレート引数が推論できる場合 実際のテンプレート引数リストから

しかし、これでは ちょうど これは,部分特化のすべてのテンプレートパラメータが推論されなければならないことを意味します。また,置換が成功し,テンプレート引数が部分特化の(置換された)テンプレートパラメータに一致しなければならないことも意味します(と思われます)。なお、私は ここで スタンダードでは、置換された引数リストと供給された引数リストとの比較を指定します。