就我个人而言,我会使用类似的技术这个答案 https://stackoverflow.com/a/29269216/1120273它定义了基于的比较函数operator<()
产生严格的弱秩序。对于具有 null 值的类型,这意味着比较始终会产生结果false
这些操作将被定义为operator<()
对所有非空值提供严格的弱顺序is_null()
test.
例如,代码可能如下所示:
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 装箱,因此提供不同 NaN 值的顺序并将 NaN 值排列在合适的位置甚至可能是合理的。例如,使用底层位表示提供浮点值的总顺序,这可能适合使用对象作为有序容器中的键,尽管该顺序可能与由创建的顺序不同operator<()
.