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

Олег Фатхиев
Олег Фатхиев

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

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);
}
6
рейтинг
в разработке
14 комментариев
yndx-antoshkka

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

yndx-antoshkka
Andrey Davydov

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

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

void test(D * d) {
    std::variant<B*> v(d);
    v == d;
}
Andrey Davydov
Олег Фатхиев

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

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

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

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

Andrey Davydov
yndx-antoshkka

Пока что, во избежание проблем, договорились делать так:

* делать через operator<=>

* разрешать (через enable_if подобные конструкции) эти операторы ТОЛЬКО если 1 альтернатива из variant сравнима с не-variant параметром

* в случае если variant содержит valueless_by_exception() или не ту альтернативу, с которой хотим сравниться, то делать поведение аналогичное существующим операторам сравнения двух вариантов

yndx-antoshkka
yndx-antoshkka

Черновик предложения: https://apolukhin.github.io/papers/variant_spaceship.html

yndx-antoshkka
Andrey Davydov

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
Andrey Davydov

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

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

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

Andrey Davydov
Обновлено 
yndx-antoshkka

Andrey Davydov, тогда не получится использовать variant с гетерогенными контейнерами.

yndx-antoshkka
Andrey Davydov

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

Andrey Davydov
yndx-antoshkka

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

 

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

yndx-antoshkka
Andrey Davydov

yndx-antoshkka,

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

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

Нужно смириться с тем, что 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
Andrey Davydov

yndx-antoshkka, в черновике (Motivation) есть опечатки в комментарии -- "grater" -> "greater" и, возможно, не хватает "?" в конце первой строки.

Andrey Davydov
yndx-antoshkka

Andrey Davydov, спасибо, подправил (ещё не выложил обновлённую версию)

yndx-antoshkka
Обновлено 
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).
Все предложения