置換失敗はエラーではない( SFINAE ) は、テンプレートパラメータの無効な置換自体はエラーではないというC++の原則です。SFINAE という頭字語は、関連するプログラミング手法を説明するために David Vandevoorde が最初に導入しました。[ 1 ]
具体的には、オーバーロード解決の候補セットを作成する際、そのセットの候補の一部(またはすべて)は、対応するテンプレートパラメータの代わりに(推論された可能性のある)テンプレート引数が代入されたインスタンス化されたテンプレートの結果である可能性があります。特定のテンプレートの引数セットの代入中にエラーが発生した場合、コンパイラはコンパイルエラーで停止するのではなく、候補セットから潜在的なオーバーロードを削除します。ただし、C++ 標準が前述のようにそのような代入エラーを破棄することを許可している場合に限ります。[ 2 ] 1 つ以上の候補が残っていてオーバーロード解決が成功した場合、呼び出しは整形式です。
SFINAEは、 C++20で追加された概念によってほぼ置き換えられており、これらの概念によってテンプレート型の制約をより簡潔に表現することが可能になっています。
以下の例は、SFINAEの基本的なインスタンスを示しています。
struct Test { using Foo = int ; };// 定義 #1 template < typename T > void f ( typename T :: Foo ) { // ... }// 定義 #2テンプレート< typename T > void f ( T ) { // ... }int main () { f <Test> ( 10 ); //呼び出し#1。f <int> ( 10 ); // 呼び出し #2。 SFINAE のため、エラーは発生しません (int::Fooが存在しないにもかかわらず)。return 0 ; }T::Fooここで、修飾名( )で非クラス型を使用しようとすると、にはという名前のネストされた型がないf<int>ため、の推論が失敗しますが、有効な関数が候補関数のセットに残るため、プログラムは整形式です。intFoo
SFINAEは当初、無関係なテンプレート宣言が可視状態にある場合(例えば、ヘッダーファイルのインクルードなど)に不正なプログラムが生成されることを回避するために導入されましたが、後に多くの開発者がコンパイル時のイントロスペクションに役立つことを発見しました。具体的には、テンプレートがインスタンス化時にテンプレート引数の特定のプロパティを判定できるようになります。
例えば、SFINAE を使用すると、型に特定の typedef が含まれているかどうかを判断できます。
import std ;template < typename T > struct HasTypedefFoobar { // 型 "Yes" と "No" は異なるサイズを持つことが保証されています。// 具体的には sizeof(Yes) == 1 および sizeof(No) == 2 です。using Yes = char [ 1 ]; using No = char [ 2 ];template < typename C > static Yes & test ( typename C :: Foobar * );テンプレート< typename > static No & test (...);// test<T>(nullptr) の呼び出し結果の "sizeof" が sizeof(Yes) と等しい場合、最初のオーバーロードが機能し、T にはFoobar という名前のネストされた型があります。static constexpr bool value = ( sizeof ( test < T > ( nullptr )) == sizeof ( Yes )); };struct Foo { using Foobar = float ; };int main () { std :: println ( " { }" , HasTypedefFoobar <int> :: value ); std :: println ( "{}" , HasTypedefFoobar <Foo> :: value ) ; return 0 ; }Tネストされた型が定義されている場合Foobar、最初のインスタンス化はtest機能し、ヌルポインタ定数は正常に渡されます。(そして、式の結果として得られる型は ですYes。) 機能しない場合、使用可能な関数は 2 番目の関数のみでありtest、式の結果として得られる型は ですNo。 省略記号は、任意の引数を受け入れるだけでなく、変換ランクが最も低いため、可能であれば最初の関数の呼び出しが優先され、曖昧さが解消されるという理由でも使用されます。
C++11では、上記のコードは以下のように簡略化できます。
import std ;template < typename T , typename = void > struct HasTypedefFoobar : std :: false_type {};template < typename T > struct HasTypedefFoobar < T , std :: void_t < typename T :: Foobar >> : std :: true_type {};struct Foo { using Foobar = float ; };int main () { std :: println ( " { }" , HasTypedefFoobar <int> :: value ); std :: println ( "{}" , HasTypedefFoobar <Foo> :: value ) ; return 0 ; }ライブラリの基本v2(n4562)提案における検出イディオムの標準化により、上記のコードは次のように書き換えることができます。
import std ;template < typename T > using HasTypedefFoobarUnderlying = typename T :: Foobar ;struct Foo { using Foobar = float ; };int main () { std :: println ( "{}" , std :: is_detected < HasTypedefFoobarUnderlying , int >:: value ); std :: println ( "{}" , std :: is_detected < HasTypedefFoobarUnderlying , Foo >:: value ); return 0 ; }