【问题标题】:Checking for three-way-comparison operator support at compile time在编译时检查三向比较运算符支持
【发布时间】:2023-08-15 18:17:01
【问题描述】:

我想有条件地在我的代码中启用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://*.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 是一项要求。
  • 比较运算符仅针对具有相同Nthing&lt;int N&gt;s 定义。
  • 必须显式删除不匹配的Ns 的运算符,否则编译器将决定将operator int 隐式应用于双方,并改用int 比较 (see linked question)。
  • 预期的行为是第 40 行和第 43 行(已标记)无法编译。

现在,我(认为)需要有条件地检查 operator &lt;=&gt; 支持的原因是:

  • 代码需要编译为 C++14、17 和 20。
  • 如果我根本不重载&lt;=&gt;,那么像thing&lt;0&gt;() &lt;=&gt; thing&lt;1&gt;() 这样的东西会被错误地编译(由于隐式转换为int;与其他运算符的情况相同)。换句话说:默认的operator &lt;=&gt; 并不适用于所有情况,所以我不能任其发展。
  • 如果我总是同时编写&lt;=&gt; 重载,则程序无法编译为 C++14 和 C++17,或者可能无法在 C++20 实现不完整的编译器上编译(尽管我没有遇到过这种情况)。

只要我手动设置SPACESHIP_OPERATOR_IS_SUPPORTED,上面的代码就满足所有要求,但我希望它是自动的。

所以,我的问题是:有没有办法在编译时检测对operator &lt;=&gt; 的支持,并有条件地启用代码(如果存在)?还是有其他方法可以使 C++14 到 20 的工作?

我处于预编译的心态,但如果有一些神奇的模板解决方案,那也可以。我真的很想要一个独立于编译器的解决方案,但至少我希望它能够在 GCC(5.x 及更高版本)和 MSVC(最好是 2015 年及更高版本)上运行。

【问题讨论】:

    标签: c++ c++14 portability c++20 spaceship-operator


    【解决方案1】:

    这就是功能测试宏的用途。有一个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 以有条件地提供&lt;=&gt;

    #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
    

    您不需要提供 both 默认的&lt;=&gt; 所有关系运算符。这就是为什么我们有&lt;=&gt;:所以你可以自己写&lt;=&gt;。你仍然需要提供operator==,但这只是因为你正在做一些特别的事情需要delete&lt;=&gt;

    【讨论】:

    • @JasonC 好的,是的,因为你要删除你需要默认并特别删除相等性。已更新。
    • @Barry 作为一个附带问题:是否可以安全地假设当且仅当支持 spaceship 运算符时,依赖于 ==!= 的 C++20 行为也存在?还是您认为在 17->20 过渡期间出现的编译器版本具有一个特性但没有另一个特性?
    • @JasonC 如果你检查201907,是的。
    • 所以我一直在阅读功能测试宏文档;关于&lt;=&gt; 的主题,是否有特别的理由要检查 201907 而不是 201711? 201711 引用 P0768R1 和 201907 引用 P1614R2。该宏的__cpp_impl 版本也可以追溯到201711,尽管我不知道它的重要性。我无法解释推荐文档。
    • @JasonC &lt;=&gt; 的设计在标准化过程中发生了很大变化,所以选择最新的。