编译时检查三路比较运算符支持情况。

4

我希望可以根据当前编译器版本和命令行选项的支持情况,在我的代码中有条件地启用 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 https://dev59.com/Hb7pa4cB1Zd3GeqP58mG)
    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是一个要求。
  • 仅对具有相同Nthing<int N>定义比较运算符。
  • 必须显式删除不匹配的N的运算符,否则编译器将决定隐式地将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及更高版本)上运行。

1个回答

7
这就是特性测试宏的作用。有一个文档定义了所有的宏及其值。你需要检查这些宏和值,所有供应商都同意遵守。
三路比较是一个有点棘手的功能,因为它需要语言和库的支持。虽然有一个语言级别的特性测试宏,但它并不是为用户准备的,而是为标准库作者提供条件性的功能。
因此,你真正需要做的是:
#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 <=>


1
@JasonC 好的,因为你正在删除,所以需要特别注意默认值和删除相等性。已更新。 - Barry
@Barry 作为一个附带问题:可以安全地假设,只有在支持宇宙飞船运算符的情况下,C++20 对 != 依赖于 == 的行为也存在吗?或者您认为在 17->20 过渡期间存在一些编译器版本具有其中一个特性而不具备另一个特性吗? - Jason C
1
@JasonC 如果你检查 201907,是的。 - Barry
我一直在阅读功能测试宏文档;关于 <=> 这个主题,有没有特别的原因要检查 201907 而不是 201711?201711 引用了 P0768R1,而 201907 引用了 P1614R2。虽然我不知道 __cpp_impl 版本的宏的重要性,但该宏的版本也可以追溯到 201711。我在解释推荐文档方面遇到了麻烦。 - Jason C
1
@JasonC <=> 的设计在标准化过程中发生了很大的变化,因此只需选择最新的版本即可。 - Barry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接