【问题标题】:sorting vector<variant<...>> does not work correctly via operator<排序向量<variant<...>> 无法通过 operator< 正常工作
【发布时间】:2019-01-16 18:11:12
【问题描述】:

我想通过两个自定义类的成员返回值对 std::variant 类型的 std::vector 进行排序。请参阅下面的代码。

现在,使用

std::sort(std::begin(shapes), std::end(shapes), [](auto const& a, auto const& b){
        return std::visit([](auto const& s) { return s.area(); }, a)
            < std::visit([](auto const& s) { return s.area(); }, b);
        });

似乎确实有效,但它非常难看。由于 std::variants operator

代码:

#include <algorithm>
#include <iostream>
#include <variant>
#include <vector>

constexpr double pi = 3.141592865;

struct Square {
    double d{};
    double area() const { return d * d; }
};

struct Circle {
    double r{};
    double area() const { return pi * r * r; }
};

// comparison operator for using std::sort(begin, end);
template <class S, class T>
double operator<(S const& a, T const& b) {
    return a.area() < b.area();
}

int main (int, char**)
{
    std::vector<std::variant<Square, Circle>> shapes;

    shapes.push_back(Circle{2});
    shapes.push_back(Square{2});
    shapes.push_back(Circle{1});
    shapes.push_back(Square{3});

    for (auto const& e: shapes)
    { std::cout << std::visit([](auto const& x) { return x.area(); }, e) << "\n"; }

    std::cout << "\n[SORT]\n\n";
    // Does not work
    std::sort(std::begin(shapes), std::end(shapes));

    /* works
    std::sort(std::begin(shapes), std::end(shapes), [](auto const& a, auto const& b){
            return std::visit([](auto const& s) { return s.area(); }, a)
                < std::visit([](auto const& s) { return s.area(); }, b);
            });
    */

    for (auto const& e: shapes)
    { std::cout << std::visit([](auto const& x) { return x.area(); }, e) << "\n"; }

    return 0;
}

谁能指出我正确的方向?我怀疑问题出在 operator 没有使用不同的类型。

编译命令: $ g++8.2 -std=c++17 test.cpp -o test

输出:

12.5664
4
3.14159
9

[SORT]

4
9
3.14159
12.5664

奇怪的是,我在使用 godbolts compile explorer 和 g++8.2 时遇到了编译错误,在使用 g++ trunk 时却没有。见:https://gcc.godbolt.org/z/tKJa4t

【问题讨论】:

  • 很确定变体的比较重载会比较两个变体中的 which 替代方案,然后仅当两个变体具有相同的基础类型时才直接比较它们. IE。它首先比较index()。
  • FWIW,std::visit可以同时访问多个变种,所以第一个sn-p可以是std::visit([](auto const&amp; lhs, auto const&amp; rhs) { return lhs.area() &lt; rhs.area(); }, a, b)
  • @SamVarshavchik 啊,我明白了。我想这解决了,我必须使用 std::visit

标签: c++ stl c++17


【解决方案1】:

这个:

std::sort(std::begin(shapes), std::end(shapes));

使用默认的sort 比较,即operator&lt;operator&lt; on std::variantdefined as 比较索引first,然后,如果两个变体具有相同的替代方案,则比较基础值。

换句话说:

using V = std::variant<char, int>;
V v1(static_cast<char>(42));  // holds a char
V v2(15);                     // holds an int
v1 < v2;                      // this is true, because 'char' comes before 'int'

因此,当您对variants 进行排序时,您并没有按区域排序。您有效地按元组(index, area) 进行排序。我们可以写出很长的路要走:

std::sort(std::begin(shapes), std::end(shapes),
    [](auto const& lhs, auto const& rhs)
    {
        auto tied = [](auto const& x) {
            return std::make_tuple(
                // the index
                x.index(),
                // the area
                std::visit([](auto const& e){ return e.area(); }, x)
                );
        };
        return tied(lhs) < tied(rhs);
    });

这给出了与原始示例相同的顺序。然后,如果您删除元组的 x.index() 部分,您将获得所需的排序。

但只使用多次访问会更短:

std::sort(std::begin(shapes), std::end(shapes),
    [](auto const& lhs, auto const& rhs)
    {
        std::visit([](auto const& x, auto const& y){
            return x.area() < y.area();
        }, lhs, rhs);
    });

在 C++20 中使用范围和投影会变得更短:

std::ranges::sort(shapes, std::less(), [](auto const& x){
    return std::visit([](auto const& e){ return e.area(); }, x);
    });

【讨论】:

    【解决方案2】:

    您误解了std::variantoperator&lt; 的工作原理。它首先比较索引,只有当索引相等时,才会比较值:https://en.cppreference.com/w/cpp/utility/variant/operator_cmp。对于不相等的索引,如果第一个变体上的索引小于第二个变体上的索引,则返回 true

    【讨论】:

      猜你喜欢
      • 2020-12-14
      • 1970-01-01
      • 2012-03-29
      • 2018-07-18
      • 1970-01-01
      • 1970-01-01
      • 2016-01-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多