вектор сортировки> неправильно работает через оператора - PullRequest
0 голосов
/ 16 января 2019

Я хотел отсортировать std :: vector типа std :: option с двумя пользовательскими классами по их возвращаемому значению члена. Смотрите код ниже.

Теперь, используя

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 :: варианты <работает с соответствующими значениями, я подумал, что предоставление шаблонного оператора сравнения будет выглядеть лучше. Но почему это не работает? </p>

Код:

#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;
}

Кто-нибудь может указать мне правильное направление? Я подозреваю, что проблема заключается в том, что оператор <</em> не работает с разными типами.

Команда компиляции: $ g ++ 8.2 -std = c ++ 17 test.cpp -o test

Выход:

12.5664
4
3.14159
9

[SORT]

4
9
3.14159
12.5664

Странно, я сталкиваюсь с ошибками компиляции, когда использую проводник компиляции Godbolts и g ++ 8.2, , но нет, когда используется транк g ++. Смотри: https://gcc.godbolt.org/z/tKJa4t

Ответы [ 2 ]

0 голосов
/ 16 января 2019

Это:

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

использует сравнение по умолчанию sort, которое operator<.operator< on std::variant - это , определяемое как при сравнении по индексам сначала , а затем, если оба варианта содержат одну и ту же альтернативу, сравниваются базовые значения.

Другими словами:

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'

Поэтому, когда вы сортируете свои variant s, вы не сортируете по областям.Вы эффективно сортируете по кортежу (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);
    });
0 голосов
/ 16 января 2019

Вы неправильно понимаете, как работает std::variant operator<.Сначала он сравнивает индексы и только если индексы равны, он сравнивает значения: https://en.cppreference.com/w/cpp/utility/variant/operator_cmp. Для неравных индексов он возвращает true, если индекс по первому варианту меньше, чем по второму.

...