27

だから私は自動を書きたい!=

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

しかし、それは失礼です1。だから私は書く

// 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 {};

t == uこれは、「変換可能な型を返す有効なコードである」という型特性クラスですbool

だから私は自分を改善します!=

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()のではなく、ほぼ毎回スナーフするためです。!=これは望ましくないと思います-!=自動転送し==て否定するよりも、明示的に呼び出したいと思います。

だから、私はこの特性クラスを書きます:

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) );
}

これを解析すると、「この文は誤りです」と表示されます --と の間に存在し、とoperator!=の間TUiffoperator!=は存在しません。TU

驚くことではありませんが、私がテストしたすべてのコンパイラは、これを入力するとセグメンテーション違反を起こしました。(clang 3.2、gcc 4.8 4.7.2 インテル 13.0.1)。 私がやっていることは違法だと思いますが、標準のリファレンスを見てみたいです。(編集: 制限のない再帰的なテンプレート展開を誘発するため、私がやっていることは違法です。適用されるかどうかを判断するには、!=適用されるかどうかを確認する必要があるためです。!=#if 1

しかし、私の質問: SFINAE ベースのオーバーライドが失敗するかどうかを判断するときに「それ自体」を無視するように説得する方法、または何らかの形で自己参照の問題を取り除く方法はありますか? operator!=または、私のローの優先順位を十分に下げて、!=それ以外の場合はそれほど良くない場合でも、明示的なものが勝つようにしますか?

「存在しない」ことをチェックしないものは!=かなりうまく機能しますが、それをグローバル名前空間に挿入するほど失礼になるには十分ではありません。

目標は、私の「魔法」が導入されると、私の「魔法」なしでコンパイルされるコード!=がまったく同じことを行うことです。それ以外の場合は無効であり、私の「魔法」が作動した!=場合にのみ適切に形成されます.!= bool r = !(a==b)!=


脚注1 : を作成するtemplate<typename U, typename T> bool operator!=(U&& u, T&& t)と、SFINAE は型のすべてのペアが!=それらの間で有効であると見なします。次に、実際に を呼び出そうとすると!=、インスタンス化されてコンパイルに失敗します。それに加えて、とbool operator!=( const foo&, const foo& )により適しているため、関数を踏みにじります。私はこれらの両方を失礼だと考えています。foo() != foo()foo a, b; a != b;

4

1 に答える 1

13

あなたのアプローチの問題は、フォールバックのグローバル定義がoperator !=魅力的すぎることであり、それを除外するには SFINAE チェックが必要です。ただし、SFINAE チェックはオーバーロードの解決に対する関数自体の適格性に依存するため、型推定中に (試みられた) 無限再帰につながります。

SFINAE に基づく同様の試みは同じ壁に衝突するように思われるので、私の意見operator !=では、最初にオーバーロード解決の魅力を少し減らして、合理的に書かれた他のものを許可するのが最も健全なアプローチです (これはのオーバーロードがoperator !=優先されます。

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

#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 !=次のように定義します。

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 !=より適切なオーバーロードが存在しない場合にのみ選択されます。can_equal<>さらに、型特性が を返す場合にのみ選択されますtrue

準備した SSCCE に対してこれをテストしました。ここでは、4 つの s がandstructのオーバーロードとともに定義されています。operator ==operator !=

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 !=は type に対して定義されているtestため、行は次のように出力され#1ます。

(!==)1

2 番目のテストに関しては、operator !=はに対して定義されておらず、 に変換できないため、グローバルが機能し、 2 かかるのオーバーロードの結果を否定する必要があります。したがって、行は次のように出力する必要があります。test3test3test4operator !=operator ==const test3&#2

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

最後に、3 番目のテストには、タイプ の 2 つの右辺値オブジェクトが含まtest4れており、operator !=が定義されています (引数は に変換できるためtest4 const&)。したがって、行は次のように#3出力する必要があります。

(!=)1

そして、生成された出力が期待どおりであることを示す実際の例を次に示します。

于 2013-04-09T23:18:20.150 に答える