cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Добавить операторы сравнения std::variant с его элементами #359

Open apolukhin opened 3 years ago

apolukhin commented 3 years ago

Перенос предложения: голоса +9, -0 Автор идеи: Олег Фатхиев

Предлагаю добавить следующие операторы:

template <class... Ts, class T>
constexpr std::enable_if_t<(... || std::is_same_v<Ts, T>),
bool> operator==(const std::variant<Ts...>& lhs, const T& rhs) {
    return std::visit([&rhs](const auto& v) {
        using V = std::decay_t<decltype(v)>;
        if constexpr (std::is_same_v<V, T>) {
            return v == rhs;
        } else {
            return false;
        }
    }, lhs);
}

template <class T, class... Ts>
constexpr std::enable_if_t<(... || std::is_same_v<Ts, T>),
bool> operator==(const T& lhs, const std::variant<Ts...>& rhs) {
    return rhs == lhs;
}

template <class... Ts, class T>
constexpr std::enable_if_t<(... || std::is_same_v<Ts, T>),
bool> operator!=(const std::variant<Ts...>& lhs, const T& rhs) {
    return !(lhs == rhs);
}

template <class T, class... Ts>
constexpr std::enable_if_t<(... || std::is_same_v<Ts, T>),
bool> operator!=(const T& lhs, const std::variant<Ts...>& rhs) {
    return !(rhs == lhs);
}
apolukhin commented 3 years ago

yndx-antoshkka, 19 сентября 2018, 12:37 Идея супер! Предлагаю написать через operator<=> чтобы уменьшить количество перегрузок (+ надо заменить decay_t на remove_cv_ref_t)

Andrey Davydov, 19 сентября 2018, 14:03 А как насчет ослабить условие (... || std::is_same_v<Ts, T>)? Хочется чтобы такой код тоже работал:

struct B {};
struct D : B {};

void test(D * d) {
    std::variant<B*> v(d);
    v == d;
}

Олег Фатхиев, 19 сентября 2018, 14:58 Andrey Davydov, не совсем понятно, как быть в таком случае.

А если у нас такой variant:

std::variant<int, char> v{ (char)5 };
v == (int)5;

Если ослабить проверку, то мы будем получать тут true, что, скорее всего, не является ожидаемым поведением, так как https://en.cppreference.com/w/cpp/utility/variant/operator_cmp делает даже более строгую проверку.

Andrey Davydov, 19 сентября 2018, 16:55 Олег Фатхиев, можно сделать так, что если Ts содержит T, то дополнительно проверять, что активный вариант типа T, иначе сравнивать даже разных типов. Все равно останется неитуитивное поведение в таком коде:

variant<string_view, bool> v("aba");
v == "aba"sv;

но тут уже ничего не поделаешь.

yndx-antoshkka, 24 сентября 2018, 16:24 Пока что, во избежание проблем, договорились делать так:

yndx-antoshkka, 25 сентября 2018, 12:31 Черновик предложения: https://apolukhin.github.io/papers/variant_spaceship.html

Andrey Davydov, 25 сентября 2018, 13:07 yndx-antoshkka, кажется что смешение сравнения индексов и сравнения значений приводит к математически абсурдным результатам:

struct A {
  bool operator == (A) const { return true; }
} a;

variant<A, int> x(a);
variant<int, A> y(a);

assert(x == a);
assert(y == a);
assert(x < 0);
assert(y > 0);

Andrey Davydov, 25 сентября 2018, 13:22 Если Вы и Олег согласны, что это проблема, то по предлагаю чинить ее понижением категории до std::partial_ordering (если у T она сильнее) и в случае

(v.index() <=> i) != 0

возвращать partial_ordering::unordered.

yndx-antoshkka, 25 сентября 2018, 14:15 Andrey Davydov, тогда не получится использовать variant с гетерогенными контейнерами.

Andrey Davydov, 25 сентября 2018, 14:39 yndx-antoshkka, Вы имеете в виду ordered контейнеры с гетерогенным lookup'ом (less)? Тогда предлагаю рассмотреть следующую альтернативу: давайте "нормализовывать" variant мысленно переупорядочивая его шаблонные аргументы в каком-нибудь implementation-defined порядке (скажем, том, что задается std::type_info::before). И дальше индексы иcпользующиеся для определения operator <=> в proposal'е берутся относительно "нормализованного" варианта. Это решит проблему (x < 0) && (y > 0) из моего примера.

yndx-antoshkka, 25 сентября 2018, 15:48 Andrey Davydov, такое поведение точно будет не переносимым и взрывающим людям мозг. Нужно смириться с тем, что variant<A, int> и variant<int, A> это разные типы, и операторы сравнения с int у них ведут себя по разному.

А вот идея с функцией, сортирующей шаблоны - это мысль интересная, но для отдельного предложения.

Andrey Davydov, 25 сентября 2018, 16:26 yndx-antoshkka,

такое поведение точно будет не переносимым и взрывающим людям мозг

Я не очень хорошо понимаю значение "непереносимым" в данном контексте. Оно будет implementation defined, но поставленную задачу -- гетерогенный lookup в ordered контейнере variant'ов оно решает. Ведь есть же уже прецендент std::type_index, да его сравнение implementation-defined, зато std::set работает.

Нужно смириться с тем, что variant<A, int> и variant<int, A> это разные типы

Это только если мы не верим в транзитивность, в моем примере (x == a) и (y == a), так что не такие уж они и разные. А если добавить

template<typename V1, typename V2>
  requires(TemplateArgsAreEqualByModuloOfPermutation<V1, V2>)
std::[strong|weak]_equality operator <=> (V1 const &, V2 const &);

что лично мне кажется, куда более естественным, чем ordering вводимый предлагаемым способом, "смириться" точно не получится.

Andrey Davydov, 2 октября 2018, 6:58 yndx-antoshkka, в черновике (Motivation) есть опечатки в комментарии -- "grater" -> "greater" и, возможно, не хватает "?" в конце первой строки.

yndx-antoshkka, 2 октября 2018, 19:30 Andrey Davydov, спасибо, подправил (ещё не выложил обновлённую версию)

apolukhin commented 3 years ago

Комитет несколько пугает перегрузка операторов отличных от ==. Нужно поработать над мотивацией, возможно оставить только операторы сравнения на равенство.