在编译时检查三向比较运算符的支持
我想operator <=>根据当前版本的编译器及其命令行选项是否支持在我的代码中有条件地启用重载。例如,我希望将以下代码编译为 C++14、17 和 20(这基本上是我之前提出的问题的解决方案的续集):
#define SPACESHIP_OPERATOR_IS_SUPPORTED 1 // <--- i want this to be automatic
#if SPACESHIP_OPERATOR_IS_SUPPORTED
#include <compare>
#endif
template <int N> struct thing {
// assume an implicit conversion to a "math-able" type exists:
operator int () const { return 0; }
// define a set of comparison operators for same N on rhs:
bool operator == (const thing<N> &) const { return true; }
bool operator != (const thing<N> &) const { return false; }
bool operator < (const thing<N> &) const { return false; }
bool operator > (const thing<N> &) const { return false; }
bool operator <= (const thing<N> &) const { return true; }
bool operator >= (const thing<N> &) const { return true; }
int operator - (const thing<N> &) const { return 0; }
// but explicitly delete ops for different N:
// (see /sf/ask/4582764861/)
template <int R> bool operator == (const thing<R> &) const = delete;
template <int R> bool operator != (const thing<R> &) const = delete;
template <int R> bool operator < (const thing<R> &) const = delete;
template <int R> bool operator > (const thing<R> &) const = delete;
template <int R> bool operator <= (const thing<R> &) const = delete;
template <int R> bool operator >= (const thing<R> &) const = delete;
template <int R> int operator - (const thing<R> &) const = delete;
// but if i don't delete <=> for differing template parameters then things
// like thing<0>() <=> thing<1>() will be allowed to compile because they'll
// be implicitly converted to an int. so i *have* to delete it when supported.
#if SPACESHIP_OPERATOR_IS_SUPPORTED
std::strong_ordering operator <=> (const thing<N> &) const = default;
template <int R> std::strong_ordering operator <=> (const thing<R> &) const = delete;
#endif
};
int main () {
thing<0> t0;
thing<1> t1;
(void)(t0 == t0); // line 39
//(void)(t0 == t1); // line 40
#if SPACESHIP_OPERATOR_IS_SUPPORTED
(void)(t0 <=> t0); // line 42
//(void)(t0 <=> t1); // line 43
#endif
}
所以,首先快速解释一下:
- 隐式
operator int是一个要求。 - 比较运算符只为
thing<int N>具有相同 的 s定义N。 N必须明确删除不匹配s的运算符,否则编译器将决定隐式应用于operator int双方并int改为使用比较(请参阅链接问题)。- 预期行为是第 40 行和第 43 行(标记)无法编译。
现在,我(认为)需要有条件地检查operator <=>支持的原因是:
- 代码需要编译为 C++14、17 和 20。
- 如果我根本不重载
<=>,那么诸如此类thing<0>() <=> thing<1>()的东西将被错误地允许编译(由于隐式转换为int; 与其他运算符的情况相同)。换句话说:默认operator <=>值并不适用于所有情况,所以我不能就这样。 - 如果我总是编写两个
<=>重载,那么程序将无法编译为 C++14 和 C++17,或者可能在具有不完整 C++20 实现的编译器上编译(尽管我没有遇到过这种情况)。
只要我手动设置SPACESHIP_OPERATOR_IS_SUPPORTED,上面的代码就满足所有要求,但我希望它是自动的。
所以,我的问题是:有没有办法在编译时检测对 的支持operator <=>,并有条件地启用代码(如果存在)?或者有没有其他方法可以使 C++14 到 20 工作?
我处于预编译器的心态,但如果有一些神奇的模板解决方案,那也行。我真的很喜欢独立于编译器的解决方案,但至少我希望它适用于 GCC(5.x 及更高版本)和 MSVC(理想情况下为 2015 及更高版本)。
回答
这就是功能测试宏的用途。有一个常设文件定义了所有的宏及其值;这些是您检查的宏和值,所有供应商都同意遵守。
特别是三向比较有点棘手,因为这是一个需要语言和库支持的功能。有一个语言级别的功能测试宏,但它不是为您(用户)设计的,它是为标准库作者有条件地提供该功能而设计的。
所以你真正需要做的是:
#if __has_include(<compare>)
# include <compare>
# if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907
# define SPACESHIP_OPERATOR_IS_SUPPORTED 1
# endif
#endif
现在在您的其余代码中,您可以检查#ifdef SPACESHIP_OPERATOR_IS_SUPPORTED以有条件地提供<=>:
#ifdef SPACESHIP_OPERATOR_IS_SUPPORTED
bool operator==(const thing<N> &) const = default;
std::strong_ordering operator<=>(const thing<N> &) const = default;
template <int R> bool operator==(const thing<R> &) const = delete;
template <int R> std::strong_ordering operator<=>(const thing<R> &) const = delete;
#else
bool operator==(const thing<N> &) const { return true; }
bool operator!=(const thing<N> &) const { return false; }
bool operator< (const thing<N> &) const { return false; }
bool operator> (const thing<N> &) const { return false; }
bool operator<=(const thing<N> &) const { return true; }
bool operator>=(const thing<N> &) const { return true; }
template <int R> bool operator==(const thing<R> &) const = delete;
template <int R> bool operator!=(const thing<R> &) const = delete;
template <int R> bool operator< (const thing<R> &) const = delete;
template <int R> bool operator> (const thing<R> &) const = delete;
template <int R> bool operator<=(const thing<R> &) const = delete;
template <int R> bool operator>=(const thing<R> &) const = delete;
#endif
你并不需要提供两个拖欠<=> 和所有的关系运算符。这就是为什么我们有<=>: 所以你可以<=>自己写。您仍然需要提供,operator==但只是因为您正在做一些特别的事情需要delete <=>.