std::variant, operator== and pattern matching
I was thinking, wouldn't adding equality comparison support between `std::variant` and one of its unambiguous alternatives help simplify potential pattern matching designs?
For example, imagine something like this (hypothetical syntax):
std::variant<int, std::string> v = 42;
match (v) {
case 42 => handle_int();
case "hello" => handle_string();
case _ => handle_other();
}
The idea would be to take advantage of something C++ already has: `operator==`. At the end of the day, matching is fundamentally a comparison, so why not build on the existing equality semantics?
For `std::optional`, we can get this with its existing comparison operators and the most trivial pattern matching compiler support:
std::optional<int> o = 42;
match (o) {
case 42 => handle_value();
case std::nullopt => handle_empty();
case _ => handle_other();
}
// Which would translate to:
if (o == 42)
handle_value();
else if (o == nullopt)
handle_empty();
else
handle_other();
And if we had something like `std::match_projection<Subject, Pattern>`, we could also extract the actual matched value instead of having to manually call `std::get`, dereference an optional, etc.
Basically, after a successful comparison, `match_projection` would tell the compiler what to extract based on the subject and pattern types and we could optionally bind that value using something like a `as <declaration>` syntax.
For example:
std::optional<int> o = 42;
std::optional<int> other_opt = 42;
match (o) {
// No binding needed
case 42 => foo();
// match_projection<optional<int>, int> -> int&
case 42 as int& x => use(x);
// match_projection<optional<int>, nullopt_t> -> void
case std::nullopt => handle_empty();
// match_projection<optional<int>, optional<int>>
// -> optional<int>&
case other_opt as auto& x => use(x);
}
The same would apply to `std::variant`, assuming we also extend its `operator==` to support comparisons with individual alternatives:
match (v) {
case 42 as int& x => use(x);
case "hello" as std::string& s => use(s);
// Only need to define operator== for this helper for this to work
case indexed<0> => handle_any_int();
// Or under this form for the sake of disambiguation
case indexed<0>(10) => handle_ten();
// Also provide match_projection<indexed<0>> to enable binding
case indexed<0>(20) as int& x => use(x);
case _ => handle_other();
}
Here, `indexed<>` would just be a hypothetical helper for explicitly selecting which alternative to compare against. It would only need an appropriate `operator==` to work with matching, and a corresponding `match_projection` specialization if we also wanted to extract the alternative.
This way, the matching itself wouldn't need to know anything about `variant`, `optional` or even user-defined types. It would just use their equality operators, while `match_projection` would tell it what to extract when we actually want to bind something, which is just syntactic sugar.
I'm curious what you think about this approach. It feels like we could get quite a bit of useful pattern matching functionality without introducing too much new language machinery or convoluted new language traits and APIs.
Also do you think std::variant being equality comparable with instances of its alternatives would be an useful addition on its own?
std::variant<int, std::string> v = 42;
if (v == "not my string") ...
// Which is simpler than
if (std::holds_alternative<std::string>(v) && std::get<std::string>(v) == "not my string") ...
Syntax seems nice on its own...
https://redd.it/1x1wh77
@r_cpp
Post #25830
3