我一直想通过SFINAE和奇怪的重复模板模式成语来实现总排序。总体思路如下:
<
,>
等。)为简单起见,我在此示例中忽略了==
和!=
运算符。
关系运算符检测
我首先定义类以静态检查类是否定义了特定功能。例如,我在这里检测到less-than运算符的存在,或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;
总排序
然后我定义了从给定运算符实现总排序的类,例如,为了定义来自less-than运算符的总排序,我将使用以下内容:
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
),简化基础class(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 :(得分:2)
这个问题的主要问题是当A
被实例化时has_less<A>
是一个不完整的类型(在total<A>
实例化为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, "");
,然后将静态断言更改为has_less2<A>
。因为A
在A
获取其less-than运算符后被实例化,所以此断言成功。
使代码成功的一种方法是转发声明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;
}
};
的基类已经解决了。像这样:
const
我知道这不是你想要的,但是如果CRTP设置自动发生,在派生类中不需要任何特殊的调整,它会更好。但这仍然可以为您提供一些有助于您找到合适解决方案的见解。我还会考虑更多,如果我想出任何东西,我会更新这个答案。
还有一件事:比较成员函数应该bool operator>(const T &t) const { ...
合格。像
#include <iostream>
int main() {
for(int i = 1; i < 6; ++i) {
std::cout << "x: " << i << std::endl;
std::cout << "y: " << i << std::endl;
}
for(int i = 6; i < 10; ++i) {
std::cout << "y: " << i << std::endl;
}
}
这非常重要,可以防止很多非显而易见的问题,编译后来使用这些类的代码。