个人而言,我会使用类似
this answer 中的技术,该技术基于
operator<()
定义比较函数,生成一个严格弱序。对于具有空值的类型,其比较操作总是返回
false
,这些操作将以
operator<()
为基础定义,提供所有非空值上的严格弱序和一个
is_null()
测试。
例如,代码可能如下所示:
namespace nullable_relational {
struct tag {};
template <typename T>
bool non_null(T const& lhs, T const& rhs) {
return !is_null(lhs) && !is_null(rhs);
}
template <typename T>
bool operator== (T const& lhs, T const& rhs) {
return non_null(lhs, rhs) && !(rhs < lhs) && !(lhs < rhs);
}
template <typename T>
bool operator!= (T const& lhs, T const& rhs) {
return non_null(lhs, rhs) || !(lhs == rhs);
}
template <typename T>
bool operator> (T const& lhs, T const& rhs) {
return non_null(lhs, rhs) && rhs < lhs;
}
template <typename T>
bool operator<= (T const& lhs, T const& rhs) {
return non_null(lhs, rhs) && !(rhs < lhs);
}
template <typename T>
bool operator>= (T const& lhs, T const& rhs) {
return non_null(lhs, rhs) && !(lhs < rhs);
}
}
这将会像这样使用:
它将被用作以下目的:
#include <cmath>
class foo
: private nullable_relational::tag {
double value;
public:
foo(double value): value(value) {}
bool is_null() const { return std::isnan(this->value); }
bool operator< (foo const& other) const { return this->value < other.value; }
};
bool is_null(foo const& value) { return value.is_null(); }
同一主题的变体可能是使用一个比较函数的实现,该函数由比较函数参数化,并负责适当地向比较函数提供参数。例如:
namespace compare_relational {
struct tag {};
template <typename T>
bool operator== (T const& lhs, T const& rhs) {
return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs == rhs; });
}
template <typename T>
bool operator!= (T const& lhs, T const& rhs) {
return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs != rhs; });
}
template <typename T>
bool operator< (T const& lhs, T const& rhs) {
return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs < rhs; });
}
template <typename T>
bool operator> (T const& lhs, T const& rhs) {
return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs > rhs; });
}
template <typename T>
bool operator<= (T const& lhs, T const& rhs) {
return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs <= rhs; });
}
template <typename T>
bool operator>= (T const& lhs, T const& rhs) {
return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs >= rhs; });
}
}
class foo
: private compare_relational::tag {
double value;
public:
foo(double value): value(value) {}
template <typename Compare>
friend bool compare(foo const& f0, foo const& f1, Compare&& predicate) {
return predicate(f0.value, f1.value);
}
};
我可以想象拥有多个这些操作生成的命名空间,以支持适当的常见情况选择。另一个选项可能是不同于浮点数的排序方式,并且例如将空值视为最小或最大值。由于一些人使用NaN-boxing,因此甚至提供不同NaN值的顺序并将NaN值排列在适当的位置可能是合理的。例如,使用底层位表示提供浮点值的总序,虽然该顺序可能与
operator<()
创建的顺序不同,但可能适用于将对象用作有序容器中的键。