Why isn't std::variant allowed to equal compare with one of its alternative types?
Asked Answered
P

4

14

For example, it should be very helpful to equal compare a std::variant<T1, T2> with a T1 or T2. So far we can only compare with the same variant type.

Pricilla answered 20/3, 2019 at 18:15 Comment(4)
I think c++20 just added it to the draft with the mothership proposalFloyfloyd
@GuillaumeRacicot In that same link, I believe it's saying the int would be implicitly cast to std::variant<int, std::string> to satisfy the comparison function signature. For an int, that's probably trivial. But for a string, that could mean a copy.Clarissa
Shouldn't the compare also work in C++17? the operator== is a freestanding function. And variant has a non-explicit conversion-constructor, so variant<T1, T2>() == T1() should convert T1 to variant<T1, T2> and it shuould find the freestanding operator==. Actually after a check I found out that it does not work. Why is that so?Centroid
Actually following code works: struct X { template<class T> X(T&&) {} }; bool operator==(X const&, X const&); X x(10); bool b = x==10; So it seemes to be related to operator== being a template.Centroid
L
6

It is an arbitrary decision by the standards committee.

Ok, not quite arbitrary. The point is you have a scale* of strictness of comparison, with points such as:

  • Most-strict: Only variants can equal each other, and they need to match both in the sequence-of-alternatives (i.e. the type), the actual alternative (the index, really, since you can have multiple identical-type alternatives) and in value.
  • Less-Strict: Equality of both the variant alternative, as a type and the value, but not of the sequence-of-alternatives, nor the index within that sequence (so the same value within two distinct alternatives of the same type would be equal).
  • Most-relaxed: Equality of the value in the active alternative, with implicit conversion of one of the elements if relevant.

These are all valid choices. the C++ committee made the decision based on all sorts of extrinsic criteria. Try looking up the std::variant proposal, as perhaps it says what these criteria are.

(*) - A lattice actually.

Levileviable answered 20/3, 2019 at 18:45 Comment(0)
C
11

A variant may have multiple duplicates of the same type. E.g. std::variant<int, int>.

A given instance of std::variant compares equal to another if and only if they hold the same variant alternative and said alternatives' values compare equal.

Thus, a std::variant<int, int> with index() 0 compares not equal to a std::variant<int, int> with index() 1, despite the active variant alternatives being of the same type and same value.

Because of this, a generic "compare to T" was not implemented by the standard. However, you are free to design your own overload of the comparison operators using the other helper utilities in the <variant> header (e.g. std::holds_alternative and std::get<T>).

Clarissa answered 20/3, 2019 at 18:25 Comment(15)
This is not an answer to the question, it's just a statement of the situation. You did not say why "a given instance of std::variant compares equal to another if and only if etc. etc."Levileviable
When comparing an int with std::variant<int, int> it could compare the active member with that int.Prepuce
@Levileviable The standard just decided it should be that way. I'm explaining the reason (in terms of how the comparison operators work in the standard) for why a generic comparison to T is not in the standard.Clarissa
@MaximEgorushkin You may want it to be like that, but that's not what the standard decided.Clarissa
@CruzJean Well, in variant visitor you cannot distinguish between the two now - the comparison can be just as oblivious. I am not sure what use case the standard had in mind, however, one can wrap an integer into a unique class and get more mileage out of that.Prepuce
A bit off-topic, but the standard libraries get outdated by the time they are integrated into the standard and then just code rot in there because standard library updates are almost non-existent. IMO, the standard commitee had better focus on modules, build and packaging system, so that people can pick and match libraries as they please.Prepuce
boost::variant has been available for decades, excluding i can't use boost people. Integrating boost::variant into std wouldn't be necessary if C++ had a package repository and a build system. Brain dump completed.Prepuce
@MaximEgorushkin boost::variant also does not support equality-comparison with one of the alternative types.Absolution
@Absolution True, i am not claiming otherwise. May be you could shed some light why variant supports duplicate types. Because if it didn't one could just do myvariant == static_cast<decltype(myvariant)>(myint) to compare. What was the more useful use case to allow duplicate types?Prepuce
@MaximEgorushkin Because it's useful to model two different states that nevertheless have the same type.Absolution
@Absolution One can always make 2 unique types from 1 type with a wrapper. IMO, that reason is not good enough.Prepuce
@MaximEgorushkin Because variant<a,b,c> always has alternative 1 being b, and if alternative of var1 is not alternative of var2 they are never equal. If you go down the path of "well, I'm sure they meant variant<int, double> when they said variant<int, int, double>", you end up with javascript "1"==1 insanity. If you want to go further, look up "sum types" in type theory.Bismuthous
@Yakk-AdamNevraumont Could not understand your logic, sorry.Prepuce
What about an operator== between variant and T which only participates in overload resolution if T is one of the alternative types and the alternative types in the variant are unique?Centroid
@Centroid I like that, and that's personally how I'd have done it - nice and easy impl too. Just needs an index() check plus a template visitor. The index() check just being to make sure std::variant<A, B> holding A which is comparable to B still fails (so as not to fly in the face of the default comparison of two variants).Clarissa
L
6

It is an arbitrary decision by the standards committee.

Ok, not quite arbitrary. The point is you have a scale* of strictness of comparison, with points such as:

  • Most-strict: Only variants can equal each other, and they need to match both in the sequence-of-alternatives (i.e. the type), the actual alternative (the index, really, since you can have multiple identical-type alternatives) and in value.
  • Less-Strict: Equality of both the variant alternative, as a type and the value, but not of the sequence-of-alternatives, nor the index within that sequence (so the same value within two distinct alternatives of the same type would be equal).
  • Most-relaxed: Equality of the value in the active alternative, with implicit conversion of one of the elements if relevant.

These are all valid choices. the C++ committee made the decision based on all sorts of extrinsic criteria. Try looking up the std::variant proposal, as perhaps it says what these criteria are.

(*) - A lattice actually.

Levileviable answered 20/3, 2019 at 18:45 Comment(0)
M
5

I can't answer the why part of the question but since you think it would be useful to be able to compare a std::variant<T1, T2> with a T1 or T2, perhaps this can help:

template<typename T, class... Types>
inline bool operator==(const T& t, const std::variant<Types...>& v) {
    const T* c = std::get_if<T>(&v);

    return c && *c == t; // true if v contains a T that compares equal to t
}

template<typename T, class... Types>
inline bool operator==(const std::variant<Types...>& v, const T& t) {
    return t == v;
}
Madonnamadora answered 20/3, 2019 at 22:38 Comment(0)
K
1

You can actually write this yourself! If you aren't so inclined to override equals operators (which in my opinion you shouldn't do with std classes), we can write with templates and type_traits the desired functionality.

#include <variant>
#include <type_traits>

template<class Variant, class T>
struct compare_variant;

template<class T, class ... Ts>
struct compare_variant<std::variant<Ts...>, T>
{
    static bool apply(std::variant<Ts...> obj1, T obj2)
    {
        return ((std::is_same<Ts, T>::value 
                && std::holds_alternative<T>(obj1) 
                && obj1 == obj2)||...);
    }
};

Note that this will only work if T is a member of Ts.... It is certainly possible to solve this for any arbitrary T but it requires slightly more complicated template machinery. Let me know if you would like this snippet as well.

Keynes answered 7/3 at 9:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.