2013-04-09 7 views
26

だから私は自動!=を書きたい:テンプレートSFINAEでこの文を避けるにはどうすればいいですか?

template<typename U, typename T> 
bool operator!=(U&& u, T&& t) { 
    return !(std::forward<U>(u) == std::forward<T>(t)); 
} 

それは失礼です。だから私が言うタイプの特性クラスがある

// T() == U() is valid? 
template<typename T, typename U, typename=void> 
struct can_equal:std::false_type {}; 

template<typename T, typename U> 
struct can_equal< 
    T, 
    U, 
    typename std::enable_if< 
     std::is_convertible< 
     decltype(std::declval<T>() == std::declval<U>()), 
     bool 
     >::value 
    >::type 
>: std::true_type {}; 

を書く「boolに変換型を返すt == u有効なコードです」。

だから、私は!=を改善:

template<typename U, typename T, 
    typename=typename std::enable_if<can_equal<T,U>::value>::type 
> 
bool operator!=(U&& u, T&& t) { 
    return !(std::forward<U>(u) == std::forward<T>(t)); 
} 

==が存在する場合は、今では唯一の有効なオーバーライドです。悲しいことに、それは少し欲張りです:

struct test { 
}; 
bool operator==(const test&, const test&); 
bool operator!=(const test&, const test&); 

それはほとんどすべてのtest() != test()ではなく、上記の!=が呼び出されるまでSNARFますよう。私はこれが望ましくないと思う - むしろ、==に自動転送よりも明示的に!=を呼んでネゲートしたいと思う。

template<typename T, typename U,typename=void> 
struct can_not_equal // ... basically the same as can_equal, omitted 

T != Uが有効であるかどうかをテストします。

だから、私はこの特性クラスを記述します。

次のように私たちは、その後!=を強化:、あなたがそれを解析すると、「この文は偽である」と言う

template<typename U, typename T, 
    typename=typename std::enable_if< 
    can_equal<T,U>::value 
    && !can_not_equal<T,U>::value 
    >::type 
> 
bool operator!=(U&& u, T&& t) { 
    return !(std::forward<U>(u) == std::forward<T>(t)); 
} 

からTUの間に存在していないoperator!=operator!=場合に限っTUの間に存在します。

驚くべきことではありませんが、私がテストしたすべてのコンパイラは、これを与えたときにsegfaultsをテストしました。 (clang 3.2、gcc 4.8 4.7.2 intel 13.0.1)。 私がやっていることは違法だと思うが、私は標準的な参照を見たいと思う。(編集:!=が適用されるかどうかを調べるために、無制限の再帰的テンプレート展開を誘導するため、私が行っていることは違法です。!=が適用されるかどうかを確認する必要があります。 )。

しかし、私の質問:SFINAEベースのオーバーライドに、それが失敗するかどうかを決めるときに "自分"を無視する方法を教えてもらえませんか、何らかの形で自己参照の問題を取り除く方法はありますか?または、私のoperator!=の優先順位を低くして、明示的に!=が勝つようにしてください。

!=が存在しません」をチェックしていないものは、うまく動作しますが、グローバル名前空間に挿入するのと同じくらい無防備ではありません。

私の "魔法"なしでコンパイルするコード!=は、 "魔法" !=が導入されたときとまったく同じことをします。 !=が無効である場合に限り、bool r = !(a==b)は私の "魔法" !=がキックされていればよい。


脚注:あなたはtemplate<typename U, typename T> bool operator!=(U&& u, T&& t)を作成する場合は、SFINAEは種類のすべてのペアは、それらの間の有効な!=を持っていると思います。実際に!=を呼び出すと、インスタンス化され、コンパイルに失敗します。さらに、とfoo a, b; a != b;の方が適しているので、bool operator!=(const foo&, const foo&)の機能を踏襲しています。私はこれらの無礼の両方をすることを検討します。

+0

私はあなたが認識していると確信しているが、 '' から名前空間std :: rel_ops'は 'これの実用的な(ナイーブ)バージョンがあります。 –

+2

非タイプのパラメータのデフォルトのテンプレート引数を入れ替えて、簡単にコピーできる場所にSSCCEを指定してください。 – Xeo

+1

+1クレイジーなことをするためだけです。再帰SFINAE ...うわー:-P –

答えて

11

あなたのアプローチの問題点は、operator !=のフォールバックグローバル定義があまりにも魅力的であることのようだ、とあなたはそれを除外するためにSFINAEチェックが必要です。しかし、SFINAEチェックは、関数自体が過負荷解決の適格性に依存しているため、型減算中に無限再帰を試みる(試みた)。

SFINAEをベースにした同様の試みが同じ壁面でクラッシュすることがあるようですので、まず最初に、operator !=を少しオーバーレイの解像度に魅力的にして、 、合理的に書かれています(これは一瞬で明らかになります)operator !=の過負荷が優先されます。

を考えると、あなたが提供する型特性can_equal

template<typename T, typename U> 
bool is_not_equal(T&& t, U&& u) 
{ 
    return !(std::forward<T>(t) == std::forward<U>(u)); 
} 

template< 
    typename T, 
    typename... Ts, 
    typename std::enable_if<can_equal<T, Ts...>::value>::type* = nullptr 
    > 
bool operator != (T const& t, Ts const&... args) 
{ 
    return is_not_equal(t, args...); 
} 

私の知る限りでは、operator !=のいずれかのオーバーロードが定義されます正確に2:私は、フォールバックoperator !=をこのように定義します

#include <type_traits> 
#include <functional> 

template<typename T, typename U, typename=void> 
struct can_equal : std::false_type {}; 

template<typename T, typename U> 
struct can_equal< 
    T, 
    U, 
    typename std::enable_if< 
     std::is_convertible< 
     decltype(std::declval<T>() == std::declval<U>()), 
     bool 
     >::value 
    >::type 
>: std::true_type {}; 

関数のパラメータ(引数パックなし)は、オーバーロードの解決に適しています。したがって、前述のフォールバックバージョンoperator !=は、より良いオーバーロードが存在しない場合にのみ選択されます。さらに、can_equal<>タイプの形質がtrueを返す場合にのみ選択されます。

4つの struct sは operator ==operator !=のいくつかのオーバーロードと一緒に定義されている、私はあなたが準備しSSCCEに対してこれをテストしてみた

、:

struct test { }; 

bool operator==(const test&, const test&) { std::cout << "(==)"; return true; } 
bool operator!=(const test&, const test&) { std::cout << "(!==)"; return true; } 

struct test2 { }; 

struct test3 { }; 
bool operator == (const test3&, const test3&) 
{ std::cout << "(==)"; return true; } 

struct test4 { }; 

template<typename T, 
     EnableIf< std::is_convertible< T, test4 const& >::value >... > 
bool operator == (T&&, T&&) { std::cout << "(==)"; return true; } 

template<typename T, 
     EnableIf< std::is_convertible< T, test4 const& >::value >... > 
bool operator != (T&&, T&&) { std::cout << "(!=)"; return true; } 

所望の出力が生成されていることを確認し、あなたが何をしたかミラーリングするにはフォールバックoperator !=のあなたの元のバージョンでは、私がis_not_equal()にプリントアウトを追加:ここで

template<typename T, typename U> 
bool is_not_equal(T&& t, U&& u) 
{ 
    std::cout << "!"; // <== FOR TESTING PURPOSES 
    return !(std::forward<T>(t) == std::forward<U>(u)); 
} 

はあなたの元からの3つのテストがあります十分:

最初のテストに関して
std::cout << (a != b) << "\n"; // #1 
std::cout << (test3() != test3()) << "\n"; // #2 
std::cout << (test4() != test4()) << "\n"; // #3 

operator !=は、タイプtestのために定義されているので、ライン#1を印刷する必要があり:

(!==)1 

第二の試験に関して、operator !=test3、及びtest3に対して定義されないありますtest4に変換できないため、グローバルoperator !=が再生され、operator ==のオーバーロードの結果を無効にする必要があります。。したがって、ライン#2が印刷されなければならない:(引数はtest4 const&に変換しているため)

!(==)0 // operator == returns true, and is_not_equal() negates it 

最後に、第3の試験は、operator !=が定義されているタイプtest4二右辺値オブジェクトを含みます。そのため、ライン#3は印刷する必要があります:

(!=)1 

そしてここでは、生成される出力が期待される一つであることを示すlive exampleです。

+0

+1、私のアプローチよりもはるかに優れています。 – ipc

+0

@ipc:ありがとうございますが、OPのテストケースと同じ結果が得られないようです。あるいは、私はそれを誤解しているかもしれません。副作用として、OPによって提供されたSSCCEはClangとGCCで異なる結果を出しますので、どちらのコンパイラが信頼できるか疑問に思っています –

+0

奇妙なことは、これが '13.5 /パックは正確に1つの関数パラメータとしてカウントされます。これが標準によって意図されているかどうかは不明です。 – ipc

関連する問題