[解決済み] void_t` の仕組み
質問
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
質問です。
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
を部分的な特殊化で定義されたパターンに置き換えます。
T
と
void_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:
部分的な特殊化は、与えられた実際のテンプレート引数リストにマッチします。 部分的な特殊化のテンプレート引数が推論できる場合 実際のテンプレート引数リストから
しかし、これでは ちょうど これは,部分特化のすべてのテンプレートパラメータが推論されなければならないことを意味します。また,置換が成功し,テンプレート引数が部分特化の(置換された)テンプレートパラメータに一致しなければならないことも意味します(と思われます)。なお、私は ここで スタンダードでは、置換された引数リストと供給された引数リストとの比較を指定します。
関連
-
[解決済み】C++でint型に無限大を設定する
-
[解決済み] エラーが発生する。ISO C++は型を持たない宣言を禁じています。
-
[解決済み】C++エラー:の初期化に一致するコンストラクタがありません。
-
[解決済み] 非静的データメンバの無効な使用
-
[解決済み】CMakeエラー at CMakeLists.txt:30 (project)。CMAKE_C_COMPILER が見つかりませんでした。
-
[解決済み】 while(cin) と while(cin >> num) の違いは何ですか?)
-
[解決済み] explicit キーワードの意味は?
-
[解決済み] 文字列の単語を反復処理するにはどうすればよいですか?
-
[解決済み] 1ビットのセット、クリア、トグルはどのように行うのですか?
-
[解決済み] C++11では、標準化されたメモリモデルが導入されました。その意味するところは?そして、C++プログラミングにどのような影響を与えるのでしょうか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] テスト
-
[解決済み】構造体のベクター初期化について
-
[解決済み】C-stringを使用すると警告が表示される。"ローカル変数に関連するスタックメモリのアドレスが返される"
-
[解決済み】致命的なエラー LNK1169: ゲームプログラミングで1つ以上の多重定義されたシンボルが発見された
-
[解決済み】IntelliSense:オブジェクトに、メンバー関数と互換性のない型修飾子がある
-
[解決済み】浮動小数点例外エラーが発生する: 8
-
[解決済み】「std::operator」で「operator<<」にマッチするものがない。
-
[解決済み] 数値定数の前にunqualified-idを付けて、数値を定義することを期待する。
-
[解決済み】std::cin.getline( ) vs. std::cin
-
[解決済み] スタックアロケーションにより初期化されていない値が作成された