C++ 在编译时检查三向比较运算符支持

C++ 在编译时检查三向比较运算符支持,c++,c++14,portability,c++20,spaceship-operator,C++,C++14,Portability,C++20,Spaceship Operator,我希望在代码中有条件地启用操作符重载,这取决于当前版本的编译器及其命令行选项是否支持它。例如,我希望将以下代码编译为C++14、17和20(这本质上是我之前提出的问题的续篇): \define SPACESHIP\u操作符\u受支持1//(const thing&)const{return false;} bool运算符=(const thing&)const{return true;} int运算符-(const thing&)const{return 0;} //但明确删除不同N的ops:

我希望在代码中有条件地启用
操作符
重载,这取决于当前版本的编译器及其命令行选项是否支持它。例如,我希望将以下代码编译为C++14、17和20(这本质上是我之前提出的问题的续篇):

\define SPACESHIP\u操作符\u受支持1//(const thing&)const{return false;}
bool运算符=(const thing&)const{return true;}
int运算符-(const thing&)const{return 0;}
//但明确删除不同N的ops:
//(见https://stackoverflow.com/questions/65468069)
模板布尔运算符==(常量对象&)常量=删除;
模板布尔运算符!=(const thing&)const=delete;
模板布尔运算符<(const thing&)const=delete;
模板布尔运算符>(const thing&)const=delete;
模板布尔运算符=(const thing&)const=delete;
模板int运算符-(const thing&)const=delete;
//但如果我不删除不同的模板参数,那么
//like thing()thing()将被允许编译,因为
//被隐式转换为int。因此我*必须*在受支持时删除它。
#如果支持太空船操作器
std::strong_排序运算符(const thing&)const=default;
模板std::强排序运算符(const thing&)const=delete;
#恩迪夫
};
int main(){
事物t0;
事物t1;
(void)(t0==t0);//第39行
//(void)(t0==t1);//第40行
#如果支持太空船操作器
(无效)(t0 t0);//第42行
//(void)(t0 t1);//第43行
#恩迪夫
}
因此,首先,请快速解释一下:

  • 隐式
    运算符int
    是一项要求
  • 比较运算符仅为具有相同
    N
    对象定义
  • 必须显式删除不匹配的
    N
    s的运算符,否则编译器将决定将
    运算符int
    隐式应用于两侧,并使用
    int
    比较()
  • 预期的行为是第40行和第43行(已标记)无法编译
现在,我(认为)需要有条件地检查操作员支持的原因是:

  • 代码需要编译为C++14、17和20
  • 如果我根本没有重载
    ,那么像
    thing()thing()
    这样的东西就被错误地允许编译(由于隐式转换为
    int
    ;与其他操作符的情况相同)。换句话说:默认的
    操作符
    并不是在所有情况下都适用,所以我不能任由其发展
  • 如果我总是同时编写
    重载,那么程序将无法编译为C++14和C++17,或者可能无法在C++20实现不完整的编译器上编译(尽管我没有遇到这种情况)
只要我手动设置
SPACESHIP\u OPERATOR\u受支持,上面的代码就满足所有要求,但我希望它是自动的

因此,我的问题是:是否有一种方法可以在编译时检测对
操作符的支持,并在存在的情况下有条件地启用代码?或者是否有其他方法可以让C++14到20实现这一点?


我有预编译的想法,但如果有一些神奇的模板解决方案,那也行。我真的想要一个独立于编译器的解决方案,但至少我希望它能在GCC(5.x及更高版本)和MSVC(理想情况下是2015及更高版本)上运行。

这就是功能测试宏的用途。有一个定义所有宏及其值的函数;这些是您检查的宏和值,所有供应商都同意遵守这些宏和值

特别是三方比较有点棘手,因为这是一个需要语言和库支持的特性。有一个语言级功能测试宏,但它不是为您(用户)设计的,而是为标准库作者设计的,有条件地提供该功能

所以你真正要做的是:

#如果有包含()
#包括
#如果定义了(uuu cpp_lib_三向比较)&&&&(uuu cpp_lib_三向比较)>=201907
#定义宇宙飞船\u运算符\u是否受\u支持1
#恩迪夫
#恩迪夫
现在,在代码的其余部分,您可以检查
\ifdef SPACESHIP\u OPERATOR\u是否受支持
,以有条件地提供

#支持ifdef SPACESHIP_运算符_
布尔运算符==(常量对象&)常量=默认值;
std::strong_排序运算符(const thing&)const=default;
模板布尔运算符==(常量对象&)常量=删除;
模板std::强排序运算符(const thing&)const=delete;
#否则
布尔运算符==(常数事物&)常数{返回真;}
接线员=(const thing&)const{return false;}
布尔运算符<(const thing&)const{return false;}
bool操作符>(const thing&)const{return false;}
bool运算符=(const thing&)const{return true;}
模板布尔运算符==(常量对象&)常量=删除;
模板布尔运算符=(const thing&)const=delete;
模板布尔运算符<(const thing&)const=delete;
模板布尔运算符>(const thing&)const=delete;
模板布尔运算符=(const thing&)const=delete;
#恩迪夫

您不需要同时提供默认的
和所有关系运算符。这就是为什么我们有
:所以你可以自己写
。您仍然需要提供
运算符==
,但这仅仅是因为您正在做一些特殊的事情,需要
删除

@HTNW是的,谢谢。@JasonC好的,是的,因为您正在删除,所以需要特别地默认和删除相等项。更新。@Barry作为一个附带问题:是否可以安全地假设当且仅当宇宙飞船操作员
#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
}