问题描述
我想在我的代码中有条件地启用 operator <=>
重载,这取决于当前版本的编译器及其命令行选项是否受支持。例如,我希望将以下代码编译为 C++14、17 和 20(这实际上是我之前提出的问题的 this solution 的续集):
#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 https://stackoverflow.com/questions/65468069)
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>
的N
定义。 - 必须显式删除不匹配的
N
的运算符,否则编译器将决定将operator int
隐式应用于双方并使用int
比较(see linked question ). - 预期行为是第 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 及更高版本)。
解决方法
这就是功能测试宏的用途。有一个 standing document 定义了所有的宏及其值;这些是您检查的宏和值,所有供应商都同意遵守。
三向比较特别有点棘手,因为这是一个需要语言和库支持的功能。有一个语言级别的功能测试宏,但它不是为您(用户)设计的,它是为标准库作者有条件地提供该功能而设计的。
所以你真正需要做的是:
#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
<=>
。