Skip to content

Instantly share code, notes, and snippets.

@petar-andrejic
Last active October 8, 2024 00:42
Show Gist options
  • Save petar-andrejic/d72a1219f927adb4c89e1041327eae13 to your computer and use it in GitHub Desktop.
Save petar-andrejic/d72a1219f927adb4c89e1041327eae13 to your computer and use it in GitHub Desktop.
Match statements in C++20
#include <print>
#include <variant>
template<class... Ts>
struct Pattern : Ts... {
using Ts::operator()...;
};
template<class Variant>
struct Match {
const Variant &v;
explicit Match(const Variant &v) : v(v) {};
template<class Pattern>
constexpr auto operator<<(const Pattern &p) const {
return std::visit(p, v);
}
};
#define match(v) Match(v) << Pattern
// Concept alias: use as a constraint on the match arm to forbid implicit conversion!
template<typename U, typename V>
concept Case = std::same_as<U, V>;
int main() {
std::variant<long, int, float> v = 5;
int shouldBeFive = 0;
int shouldBeSix = 0;
shouldBeSix = match(v) {
[&](Case<int> auto val){
std::println("An integer: {}", val);
shouldBeFive = val;
return 6;
},
[](auto) {
std::println("Not implemented");
return 0;
},
};
std::println("shouldBeFive is: {}", shouldBeFive);
std::println("shouldBeSix is: {}", shouldBeSix);
v = 3l;
match(v) {
[](Case<int> auto val) {
std::println("An integer: {}", val);
},
[](Case<float> auto val) {
std::println("A float: {}", val);
},
[](auto val) {
std::println("The default match (long): {}", val);
},
};
return 0;
}
@petar-andrejic
Copy link
Author

petar-andrejic commented Oct 7, 2024

Basically, std::visit sucks in syntax. The usual overload method makes visit a bit less painful, but I'd rather not wrap the block in parentheses. We also don't want implicit conversion in the overloads, but luckily in C++20 we have concepts and template lambdas! We can use std::same_as to constrain the lambda to only accept the case we considered without implicit conversion! We can then use unconstrained auto to match anything, as a default case. Exhaustive matching is already covered by std::visit at compile time

The output:

An integer: 5
shouldBeFive is: 5
shouldBeSix is: 6
The default match (long): 3

@petar-andrejic
Copy link
Author

petar-andrejic commented Oct 7, 2024

Because of constexpr everything inlines if possible, compiling down to jump tables: https://godbolt.org/z/aa38jG988 https://godbolt.org/z/qdanrqdr8 https://godbolt.org/z/EM7zK59YM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment