2017-02-13 55 views
3

我一直在通過SFINAE和Curiously Recurring模板模式成語實現總排序,現在已經有一段時間了。總的想法如下:奇怪的循環模式和Sfinae

  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 

繼承

所有這些措施,單獨工作。但是,當我使用奇怪的循環模式實際繼承基類時,所得到的類僅實現這些運算符中的一個,並且檢測算法失敗。

我的問題歸結爲一個最小的例子包括核心部分組成:操作者檢測(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是當has_less<A>被實例化一個不完整的類型(如total<A>A的基類的實例化期間) - 在該點,編譯器還不知道A具有operator <

所以,has_less<A>被實例化,其value == 0,並選擇total<A>的基類symmetry<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, "");。因爲has_less2<A>A得到它的小於運算符後被實例化,所以這個斷言成功了。你可以讓

一種方法的代碼成功是前瞻性聲明A,並宣佈全球operator <,用於比較兩個A對象,所以編譯器知道這個操作之前A的基類制定。事情是這樣的:

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安裝程序會自動發生,而不在派生類中所需的任何特殊照顧。但是這可能仍然會給你一些見解,這可以幫助你找到適當的解決方案。我還會考慮更多,如果我想出任何東西,我會更新這個答案。

還有一件事:比較成員函數應該是const合格。像

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

這是非常重要的,並會防止很多非顯而易見的問題編譯後來使用這些類的代碼。