C++ is going to have pattern matching "any year now", and it's going to make `std::variant` more ergonomic to use, but on the other hand any pattern matching feature will have to make design compromises in order to support `std::variant` and other mutually-incompatible variant-like library types of which C++ has a bunch of (pointers, unions, `std::optional`, `std::any`, `std::expected`, did I miss any?) Add to that all the zillions of third-party variant-like types (including boost::variant) that exist in the wild because the code base predates C++17 and/or doesn't want to use C++17 features for whatever reason. All this complexity could've been avoided had the language just supported real sum types from the start. Or at least from C++11 up or whatever.
As a sibling commenter noted, algebraic data types and pattern matching with compile-time exhaustiveness checking go hand in hand; I meant to mention the latter in my original comment, but left it out because I consider the latter almost implied by the former.
All I'm saying is that the current state of std::variant makes it okay enough to use type safe discriminated unions.
Visit + overload is not that far away from pattern matching in terms of readability, clang does warn on non exhaustive switch cases, etc.
> Does that really need to be part of the language though, or as long as you can code it, or have it in the standard library, it's fine?
And my answer is, yes. I don't consider `std::variant` a proper replacement, more like a crutch that may even be worse than not having anything at all, because its existence can be used as an argument against introducing language-level sum types in the future.
UPDATE: also, some advantages coming from the interaction of enums with other Rust language features:
- Because Rust doesn't have a stable ABI, the compiler is free to agressively optimize the layout of enums. The size of an enum is often equal to (rather than greater than) the size of the largest variant, if the variant has some "impossible" bit values (like null pointers and non-UTF8 chars) that can be used for storing the tag.
- Because Rust traits can (and must) be implemented outside of the type definition, you can implement a trait directly for an enum and then use that enum as a polymorhpic "trait object". In C++, if you want to polymorphically use an `std::variant` as a subclass of something, you need to define an ackward wrapper class that inherits from the parent.
using MyDiscreminatedUnion = std::variant<MyType1, MyType2, MyType3>;
MyDiscreminatedUnion var = MyType2{};
std::visit(overload {
[](MyType1 t1) {...},
[](MyType2 t2) {...},
[](auto t) {...},
}, var);namespace StlHelpers {
template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>;
template<class var_t, class... Func> auto visit(var_t & variant, Func &&... funcs)
{
return std::visit(overload{ funcs... }, variant);
}}
And then
StlHelpers::visit(var,
[](MyType1 t1) {...},
[](MyType2 t2) {...},
[](auto t) {...}
);
Does that really need to be part of the language though, or as long as you can code it, or have it in the standard library, it's fine?
What can Rust's unions do that std::variant cannot?