読者です 読者をやめる 読者になる 読者になる

C++0xのSFINAE仕様

C++

以前書いたエントリ

C++0x SFINAE問題の解決

が「よくわからない」っていう意見が(自分も含めて)けっこう多かったので、N2798のSFINAEの説明を全文翻訳しました。



14.8.2 パラグラフ6
テンプレート引数推論プロセスでのあるポイントでは、テンプレートパラメータを利用する関数型をとり、それらのテンプレートパラメーターを対応するテンプレート引数に置き換えることが必要です。
これは、あらゆる明示的に指定されたテンプレート引数が関数型に置換される場合はテンプレート引数推論の初めに行い、
また、テンプレート引数推論の最後に再度、デフォルト引数から推論もしくは得られたあらゆるテンプレート引数が置き換えられます。


14.8.2 パラグラフ7
置き換えは、関数型、テンプレートパラメータ宣言および(もしあれば)テンプレート要件(14.10.1)の中で使用される全ての型と式に起こります。
式は、非定数式を許可するsizeof、decltypeおよび他のコンテキストの内部の配列の範囲、あるいは非型テンプレート引数として現われるもののような定数式だけでなく一般的な式(つまり非定数式)も含んでいます。
[注:関数がインスタンス化される場合に限り、例外仕様中の等価な置き換えを行い、置き換えが無効の型もしくは式に帰着する場合プログラムは不適格です。]


14.8.2 パラグラフ8
置き換えが無効の型もしくは式に帰着する場合、あるいは置き換えられたテンプレート要求(14.10.1.1)を満たすことができない場合、型推論は失敗します。
無効の型もしくは式は、もし置き換えられた引数を使用して書かれた場合、不適格です。
アクセスチェックは置換プロセスの一部としては行いません。
従って、推論が成功する場合、関数がインスタンス化される場合、アクセスエラーは今までどおり生じるかもしれません。
関数型とそのテンプレートパラメータの型の直接のコンテキスト中の無効の型と式だけが、推論失敗に帰着することができます。
[注:置き換えられた型と式の評価は、クラステンプレート特殊化および(または)関数テンプレート特殊化のインスタンス化、暗黙に定義された関数等の生成のような副作用に帰着する場合があります、そのような副作用は「直接のコンテキスト」ではなく不適格なプログラムに帰着することができます。]


例:

struct X { };
struct Y {
    Y(X){}
};

template <class T> auto f(T t1, T t2) -> decltype(t1 + t2); // #1
X f(Y, Y); // #2

X x1, x2;
X x3 = f(x1, x2); // #1の推論に失敗し(X+Xができない), #2を呼ぶ


[注:型推論は次の理由で失敗するかもしれません:

・異なる長さの複数パラメータパックを含んでいるパック拡張をインスタンス化することを試みること。

・要素型を持った配列を作成することを試みること。void、関数型、参照型あるいは抽象型クラス型、あるいは0または負のサイズで配列を作成することを試みること。

例:

template <class T> int f(T[5]);
int I = f<int>(0);
int j = f<void>(0); // 無効な配列

・修飾名中のクラス型ではない型を使用することを試みること。

例:

template <class T> int f(typename T::B*);
int i = f<int>(0);

・以下のような場合にqualified-idのnested-name-specifierの中で型を使用することを試みること
  ・その型が特殊メンバ(specialized member)を含まない場合、あるいは
  ・型が要求されるところで特殊メンバが型ではない場合、あるいは
  ・テンプレートが必要なところで特殊メンバがテンプレートではない場合、あるいは
  ・非型が必要なところで特殊メンバが非型ではない場合

例:

template <int I> struct X { };
template <template <class T> class> struct Z { };
template <class T> void f(typename T::Y*){}
template <class T> void g(X<T::N>*){}
template <class T> void h(Z<T::template TT>*){}
struct A {};
struct B { int Y; };
struct C {
    typedef int N;
};
struct D {
    typedef int TT;
};

int main() {
    // 推論はこれらの場合に失敗する:
    f<A>(0); // AはメンバYを含んでいない
    f<B>(0); // BのYメンバは型ではない
    g<C>(0); // CのNメンバは非型ではない
    h<D>(0); // DのTTメンバはテンプレートではない
}


・参照型へのポインタを作成することを試みること。

・voidへの参照を作成することを試みること。

・Tがクラス型でない場合に「Tのメンバへのポインタ」を作成することを試みること。
例:

template <class T> int f(int T::*);
int i = f<int>(0);

・非型テンプレートパラメータに無効の型を渡すことを試みること。
例:

template <class T, T> struct S {};
template <class T> int f(S<T, T()>*);
struct X {};
int i0 = f<X>(0);

・テンプレート引数式、あるいは式の関数宣言の中での使用のいずれかでの無効の変換を行なうことを試みること。
例:

template <class T, T*> int f(int);
int i2 = f<int,1>(0); // 1からint*に変換できない

・パラメータがvoidの型を持っているか、その中で戻り値型が関数型、もしくは配列型である関数型を作成することを試みること。

・コンセプトインスタンスのためにconcept mapルックアップ(14.10.1.1)がそのコンセプトインスタンスに対応するconcept mapを見つけないコンセプトインスタンス中のメンバを指すqualified-idのnested-name-specifierの中で型を使用することを試みること。

・そのテンプレート要件を満たさないテンプレート引数を持ったクラスまたは関数テンプレートを使用することを試みること。
例:

concept C<typename T> { /* ... */ }
template<typename T> requires C<T> class X { /* ... */ };
template<typename T> int f(X<T>*); // #1
template<typename> int f(...); // #2
int i0 = f<int>(0); // OK: #2を呼ぶ