如何在使用std::string成员时使用默认的太空船操作符

3
我有一个结构体,其中有一些POD类型和一个字符串成员。我想使用默认的“飞船运算符”来允许对我的结构进行等式操作,但是我在字符串成员方面遇到了一些问题。据我所知,应该支持 std::string <=> ,但实际上似乎并非如此。 我有一个最小化的复现示例,以下是我从clang(版本14)获得的结果警告。奇怪的是,在godbolt中,即使回到clang 12,这个示例也不会产生任何警告/错误。我真的很感激关于我理解方面的指针,因为我对这个问题感到相当困惑。
#include <compare>
#include <string>

enum class MyEnum
{
    ZERO = 0,
    ONE = 1
};

struct MyStruct
{
    float a{};
    int b{};
    std::string c{};
    MyEnum d{};

    auto operator<=>(const MyStruct&) const = default;
};

int main()
{
    MyStruct my_struct;
    MyStruct my_other_struct;

    if(my_struct == my_other_struct)
    {
        return 0;
    }

    return 1;
}

$ clang --std=c++20 -Werror test.cpp                                                                                                                                                                                      
test.cpp:16:10: error: explicitly defaulted three-way comparison operator is implicitly deleted [-Werror,-Wdefaulted-function-deleted]
    auto operator<=>(const MyStruct&) const = default;
         ^
test.cpp:13:17: note: defaulted 'operator<=>' is implicitly deleted because there is no viable three-way comparison function for member 'c'
    std::string c{};
                ^
1 error generated.
$ clang --version                                                                                                                                                                                                         
Apple clang version 14.0.0 (clang-1400.0.29.202)

5
Apple Clang通常在支持新功能的库方面落后。 Apple Clang是“常规”Clang的一个分支。 - NathanOliver
对啊,我忘了苹果的clang不等于clang。谢谢你的快速回复! - rbv
如果您只需要相等性比较,请尝试定义默认的相等性运算符,它应该被定义为 std::string 并且应该有效。 - sklott
如果你将 auto 改为 std::strong-ordering,它会编译吗?我不知道这个版本的 clang 是否支持该特性,也没有检查的方法。 - Barry
@sklott,我实际上需要一个std::set<MyStruct>,因此它需要具有所有比较运算符。我可能应该在问题的示例中包含它,抱歉!我知道我可以编写自己的比较运算符,但我只是想使用闪亮的新C++20功能的快捷方式! - rbv
@Barry 哇,它确实可以编译!太棒了,我从来没有想过要尝试那个。非常感谢。 - rbv
1个回答

6
根据评论,看起来这个版本的clang和它的标准库:
- 实现了`<=>`的语言特性,但是 - 还没有实现`<=>`的库扩展(具体来说是为`std::string`添加`<=>`)。
当你写:
struct MyStruct
{
    float a{};
    int b{};
    std::string c{};
    MyEnum d{};

    auto operator<=>(const MyStruct&) const = default;
};

默认情况下,<=> 的含义是使用 <=> 进行逐成员比较,并返回第一个不相等的成员的值。当你使用 auto 时,这就是在要求编译器为你推断比较类别 - 但与 auto 的典型用法不同(在典型用法中,所有返回类型必须相同,并且 auto 推断为该类型),在这里我们取所有比较类别的最小值。例如,如果我们有一个成员的 <=> 返回了 weak_ordering,而另一个成员的 <=> 返回了 strong_ordering,那么默认的 <=> 将返回 weak_ordering(而不是无法编译)。基本上,它做的是你预计它会做的事情。

但是 - 有时候仅仅使用默认的 <=> 是不够的,如果你的成员之一实际上还没有提供 <=>(就像这个例子中一样)。在这种情况下,手动编写所有比较将会很繁琐。因此,为了这个原因,还有另一种方式可以设置默认的 <=>(来自P1186):你可以明确地提供一个比较类别:
struct MyStruct
{
    float a{};
    int b{};
    std::string c{};
    MyEnum d{};

    std::strong_ordering operator<=>(const MyStruct&) const = default;
};

这意味着,与其简单地进行逐成员的<=>比较,我们要做一些更复杂的操作:

  • 如果成员具有<=>,则使用它。并且它必须满足比较类别的要求(在本例中,它可能会失败,因为float与不满足strong_ordering要求的partial_ordering进行比较,所以它将返回partial_ordering)。
  • 如果成员没有提供<=>,我们会从==<合成一个三路比较。

因此,在这种情况下:

struct MyStruct
{
    float a{};
    int b{};
    std::string c{};
    MyEnum d{};

    std::partial_ordering operator<=>(const MyStruct&) const = default;
};

会表现得像:
std::partial_ordering operator<=>(const MyStruct& rhs) const {
    // a and b provide <=>, so use it
    if (auto cmp = a <=> rhs.a; cmp != 0) return cmp;
    if (auto cmp = b <=> rhs.b; cmp != 0) return cmp;

    // c doesn't yet, so synthesize one
    if (auto cmp =
        (c == rhs.c ? partial_ordering::equivalent : 
         c < rhs.c  ? partial_ordering::less :
         c > rhs.c  ? partial_ordering::greater :
                      partial_ordering::unordered); cmp != 0) return cmp;

    // and d does, so use it
    return d <=> rhs.d;
}

这种语言特性的优势(正如我在那篇论文中所描述的)是一旦 std::string 提供了 <=>,你的实现将会自动接收并变得更高效(因为上述并不是一个很好的字符串实现,你应该使用 c.compare(rhs.c) <=> 0),而无需你自己完成所有的工作。

在那个条件语句集的最后一个条件中,为什么要将partial_ordering::unordered0进行比较? - 303

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