【发布时间】:2017-02-15 10:28:09
【问题描述】:
一段时间以来,我一直想通过 SFINAE 和 Curiously Recurring Template Pattern 习语来实现全排序。大致思路如下:
- 定义检查关系运算符(
<、>等)的模板 - 定义定义总排序运算符的基类。
- 根据运算符检测定义基类。
- 从基类继承。
为简单起见,我在此示例中忽略了 == 和 != 运算符。
关系运算符检测
我首先定义类来静态检查该类是否定义了特定的功能。例如,这里我检测到小于运算符的存在,即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_less、has_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 的运算符检测在继承期间失败,但对继承的方法成功。
【问题讨论】:
-
您希望在该示例中发生什么?
-
@mascoj,理想情况下,它会编译。我将编辑我的问题。