2017-02-13 20 views
3

私は今、SFINAEとCuriously Recurring Template Patternイディオムを使って総発注を実装しています。次のように一般的な考え方は次のとおりです。不思議な反復パターンと合成

  1. は、関係演算子をチェックしたテンプレート(<>など)
  2. は全順序演算子を定義する基本クラスを定義定義します。
  3. オペレータ検出から基本クラスを定義します。
  4. 基本クラスから継承します。

簡略化のため、この例では==!=演算子は無視しています。

関係演算子検出

私が最初に静的クラスは、特定の機能を定義するかどうかをチェックするためのクラスを定義します。たとえば、ここでは、より小さい演算子、またはoperator<の存在を検出します。

template <typename T> 
class has_less 
{ 
protected: 
    template <typename C> static char &test(decltype(std::declval<C>() < std::declval<C>())); 
    template <typename C> static long &test(...); 

public: 
    enum { 
     value = sizeof(test<T>(0)) == sizeof(char) 
    }; 
}; 

template <typename T> 
constexpr bool has_less_v = has_less<T>::value; 

私は少ないし、よりオペレータから全順序を定義するために、例えば、与えられたオペレータから全順序を実装するクラスを定義

を注文合計、私は次のように使用します。

template <typename T> 
struct less_than_total 
{ 
    bool operator>(const T &t) { return t < static_cast<T&>(*this); } 
    bool operator>=(const T &t) { return !(static_cast<T&>(*this) < t); } 
    bool operator<=(const T &t) { return !(t < static_cast<T&>(*this)); } 
}; 

基本クラス

私はその後、定義実装された演算子を検出することによって残りの演算子を実装するtypedefを作成する単一の基本クラス。

template <bool B, typename T, typename F> 
using conditional_t = typename std::conditional<B, T, F>::type; 

template <typename T> 
using total_ordering = conditional_t<   // has_less 
    has_less_v<T>, 
    less_than_total<T>, 
    conditional_t<        // has_less_equal 
     has_less_equal_v<T>, 
     less_equal_total<T>, 
     conditional_t<       // has_greater 
      has_greater_v<T>, 
      greater_total<T>, 
      conditional_t<      // has_greater_equal 
       has_greater_equal_v<T>, 
       greater_equal_total<T>, 
       symmetric<T>     // symmetry 
      >         // has_greater_equal 
     >          // has_greater 
    >           // has_less_equal 
>;            // has_less 

継承

これらの手順のすべて

、個別に、作品。しかし、奇妙な反復パターンを使用して基本クラスから実際に継承すると、結果のクラスはこれらの演算子のうちの1つだけを実装し、検出アルゴリズムは失敗します。

Iは、コア部からなる最小の例に問題を煮詰めてきた:オペレータ検出(has_lesshas_greater)、全順序の実装(total)、簡略化された基本クラス(total)を、およびこれらの関係演算子を使用する単純な構造体(A)です。

#include <type_traits> 


// DETECTION 

template <typename T> 
class has_less 
{ 
protected: 
    template <typename C> static char &test(decltype(std::declval<C>() < std::declval<C>())); 
    template <typename C> static long &test(...); 

public: 
    enum { 
     value = sizeof(test<T>(0)) == sizeof(char) 
    }; 
}; 


template <typename T> 
class has_greater 
{ 
protected: 
    template <typename C> static char &test(decltype(std::declval<C>() > std::declval<C>())); 
    template <typename C> static long &test(...); 

public: 
    enum { 
     value = sizeof(test<T>(0)) == sizeof(char) 
    }; 
}; 


// TOTAL ORDERING 


template <typename T> 
struct less_than_total 
{ 
    bool operator>(const T &t) { return t < static_cast<T&>(*this); } 
    bool operator>=(const T &t) { return !(static_cast<T&>(*this) < t); } 
    bool operator<=(const T &t) { return !(t < static_cast<T&>(*this)); } 
}; 


template <typename T> 
struct symmetry 
{}; 


template <bool B, typename T, typename F> 
using conditional_t = typename std::conditional<B, T, F>::type; 


template <typename T> 
struct total: conditional_t< 
     has_less<T>::value, 
     less_than_total<T>, 
     symmetry<T> 
    > 
{}; 


// TEST 

struct A: total<A> 
{ 
    bool operator<(const A &r) 
    { 
     return true; 
    } 
}; 



int main(void) 
{ 
    static_assert(has_less<A>::value, ""); 
    static_assert(has_greater<A>::value, ""); 
    return 0; 
} 

理想的には、この例では、コンパイルだろう、しかし、私が取得:

$ clang++ a.cpp -o a -std=c++14 
a.cpp:79:5: error: static_assert failed "" 
    static_assert(has_less<A>::value, ""); 
    ^   ~~~~~~~~~~~~~~~~~~ 
a.cpp:80:5: error: static_assert failed "" 
    static_assert(has_greater<A>::value, ""); 

残念ながら、ベースクラスは継承時の演算子を検出していない、とSFINAEは、より少ないまたはそれ以上を検出しません結果のクラスの演算子。

質問やフォローアップ

私はこれが失敗した理由を、私は問題のない不思議な定期的なパターンで長時間メンバー関数の検出とメンバー型検出を行っているため、知っていただきたいと思い

。私のコードに直接問題がないと仮定すると、そのような方法でトータルオーダーを実装するための回避策はありますか?

編集

私はstd::enable_ifを使って何をしたいのサブセットを達成することができますよ。この場合、唯一の簡単な答えは、すべてをoperator<の形で実装してから、その演算子からの合計発注を実装することです。

template <typename T> 
struct total 
{ 
    template <typename U = T> 
    typename std::enable_if<has_less<U>::value, bool>::type 
    bool operator>(const T &l, const T &r) { return r < t; } 
}; 

の場合は、まだSFINAEを経由して私のオペレータの検出は、相続時に失敗しますが、継承されたメソッドのために成功した理由のための答えをしたいと思います。

+0

この例ではどのようなことが起こりそうですか? – mascoj

+0

@mascoj、理想的にはコンパイルします。私は私の質問を編集します。 –

答えて

2

これの主な問題は、Aは(total<A>Aとして基本クラスのインスタンスの間)has_less<A>がインスタンス化された不完全な型であることである - この時点では、コンパイラは、まだAを持っていることを知りませんoperator <

ので、has_less<A>は、そのvalue == 0でインスタンス化され、そしてsymmetry<A>total<A>の基底クラスのために選択されている - そうA決してその追加の演算子のいずれかを取得していません。

これがすべて確定した後、コンパイラはA::operator <の定義を確認し、Aに追加します。その後、Aは完了です。

私たちはなぜstatic_assert(has_greater<A>::value, "");が失敗するのか知っていますが、static_assert(has_less<A>::value, "");が成功するとは思わないでしょうか?結局のところ、Aにはより小さい演算子があります。問題は、has_less<A>がすでにAでインスタンス化されていて、value == 0となっています。Aが変更されても、以前にインスタンス化されたコンパイル時の値を更新するメカニズムはありません。だから成功するはずだが、このアサーションも失敗する。

これを示すには、has_lessのコピーを作成し、名前をhas_less2とし、静的アサーションをstatic_assert(has_less2<A>::value, "");に変更します。 Aがより小さい演算子を取得した後にhas_less2<A>がインスタンス化されるため、このアサーションは成功します。 Aの基底クラスが働いている前に、コンパイラは、この演算子について知っているようにコードを成功あなたが作ることができ

一つの方法は、2つのAオブジェクトを比較するために、Aを転送-宣言し、グローバルoperator <を宣言することです。このような何か:

struct A; 
bool operator < (const A &lh, const A& rh); 

struct A : total<A> { 
    friend bool operator < (const A &lh, const A& rh) { 
     return true; 
    } 
}; 

私は、これはしかし、あなたが欲しいものを本当にはないことを理解 - CRTPの設定が自動的に発生した場合には、派生クラスで必要と特別な宿泊せずに、非常に良くなります。しかし、これはまだあなたに適切な解決策を導く助けとなるいくつかの洞察を与えるかもしれません。私はこのことについてもう少し考えてみましょう。もし私が何かを考え出すなら、私はこの答えを更新します。

もう1つ:比較メンバ関数はconstである必要があります。 Like

bool operator>(const T &t) const { ... 

これは実際に重要であり、これらのクラスを後で使用するコードをコンパイルすることは非常に重要です。

関連する問題