2017-02-13 60 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 { ... 

这是非常重要的,并会防止很多非显而易见的问题编译后来使用这些类的代码。