57

http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspxで、VC++ チームは、C++11 のコア機能 "Expression SFINAE" をまだ実装していないことを公式に宣言しています。ただし、http: //www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2634.html からコピーされた次のコード例は、VC++ コンパイラで受け入れられます。

例 1:

template <int I> struct A {};

char xxx(int);
char xxx(float);

template <class T> A<sizeof(xxx((T)0))> f(T){}

int main()
{
    f(1);
}

例 2:

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);  // deduction fails on #1 (cannot add X+X), calls #2

私の質問は、「Expression SFINAE」とは何ですか?

4

1 に答える 1

72

式 SFINAE は、リンクした論文で非常によく説明されていると思います。式に関するSFINAEです。内部の式decltypeが有効でない場合は、オーバーロードの VIP ラウンジから関数をキックします。この回答の最後に規範的な文言があります。

VC++ に関する注意:完全には実装されていません。単純な式では機能する可能性がありますが、他の式では機能しません。失敗する例については、この回答に関するコメントのディスカッションを参照してください。簡単にするために、これは機能しません。

#include <iostream>

// catch-all case
void test(...)
{
  std::cout << "Couldn't call\n";
}

// catch when C is a reference-to-class type and F is a member function pointer
template<class C, class F>
auto test(C c, F f) -> decltype((c.*f)(), void()) // 'C' is reference type
{
  std::cout << "Could call on reference\n";
}

// catch when C is a pointer-to-class type and F is a member function pointer
template<class C, class F>
auto test(C c, F f) -> decltype((c->*f)(), void()) // 'C' is pointer type
{
  std::cout << "Could call on pointer\n";
}

struct X{
  void f(){}
};

int main(){
  X x;
  test(x, &X::f);
  test(&x, &X::f);
  test(42, 1337);
}

Clang を使用すると、次のように出力されます。

参照で
呼び出せた ポインタで呼び出せた
呼び出せなかった

MSVC を使用すると、コンパイル エラーが発生します。

1>src\main.cpp(20): エラー C2995: ''unknown-type' test(C,F)': 関数テンプレートは既に定義されています
1> src\main.cpp(11) : 'test' の宣言を参照してください

また、GCC 4.7.1 は十分に機能していないようです。

source.cpp: 'template decltype ((c.*f(), void())) test(C, F) [with C = X*;] の代わりに F = ボイド (X::*)()]':
source.cpp:29:17: ここから必要
source.cpp:11:6: エラー: メンバー ポインター 'f' を 'c' に適用できません。これは非クラス型 'X*' です
source.cpp: 'template decltype ((c.*f(), void())) test(C, F) [with C = int;] の代わりに F = int]':
source.cpp:30:16: ここから必要
source.cpp:11:6: エラー: 'f' は 'int' 型であるため、メンバー ポインターとして使用できません

式 SFINAE の一般的な使用は、クラスが特定のメンバー関数をサポートしているかどうかをチェックするための特性のように、特性を定義する場合です。

struct has_member_begin_test{
  template<class U>
  static auto test(U* p) -> decltype(p->begin(), std::true_type());
  template<class>
  static auto test(...) -> std::false_type;
};

template<class T>
struct has_member_begin
  : decltype(has_member_begin_test::test<T>(0)) {};

実例。(驚くべきことに、これは GCC 4.7.1 でも動作します。)

this answer of mineも参照してください。これは、別の環境で同じ手法を使用しています (別名、特性なし)。


規範的な文言:

§14.8.2 [temp.deduct]

p6テンプレート引数推定プロセスの特定の時点で、テンプレート パラメーターを利用する関数型を取得し、それらのテンプレート パラメーターを対応するテンプレート引数に置き換える必要があります。これは、明示的に指定されたテンプレート引数が関数型に代入されるときのテンプレート引数推定の開始時に行われ、デフォルト引数から推定または取得されたテンプレート引数が 置換 されるとき、テンプレート引数推定の最後に再度行われます

p7置換は、関数の型とテンプレート パラメーターの宣言で使用されるすべての型と式で発生します。式には、配列境界内または非型テンプレート引数として現れるような定数式だけでなく、 、、および非定数式を許可するその他のコンテキスト内の一般式(つまり、非定数式)も含まれます。 sizeofdecltype

p8 置換の結果が無効な型または式になる場合、型推定は失敗します。無効な型または式は、置換された引数を使用して記述した場合に不適切な形式になるものです。[...]

于 2012-09-29T16:10:34.210 に答える