Skip to content

Instantly share code, notes, and snippets.

@gatchamix
Created February 27, 2024 11:17
Show Gist options
  • Save gatchamix/ad5026d5a68aad8f34ec2a085ff1dcea to your computer and use it in GitHub Desktop.
Save gatchamix/ad5026d5a68aad8f34ec2a085ff1dcea to your computer and use it in GitHub Desktop.
#include <variant>
#include <type_traits>
#include <iostream>
#include <concepts>
#include <print>
//
#define FWD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)
//
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4584)
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winaccessible-base"
#endif
template <typename... Ts>
struct overload : Ts...
{ using Ts::operator()...; };
template <typename... Ts>
overload(Ts...) -> overload<Ts...>;
#if defined(_MSC_VER)
#pragma warning(pop)
#else
#pragma GCC diagnostic pop
#endif
//
namespace detail
{
template <typename F>
concept generic_overload = !requires { &F::operator(); };
template <typename R, typename C, typename T>
consteval auto param_is_impl(R(C::*)(T) const) -> std::decay_t<T>;
template <typename F, typename T>
concept param_is = requires { { param_is_impl(&F::operator()) } -> std::same_as<T>; };
template <typename F, typename T>
concept overloads_type = true
&& (param_is<F, std::decay_t<T>> || generic_overload<F>)
&& (std::invocable<F, T>);
template <typename F, typename... Ts>
consteval auto check_overload_set()
{ static_assert((... ||!std::invocable<F, Ts>), "overload [F] matches no alternative [Ts]"); };
template <typename T, typename... Fs>
consteval auto check_alternatives()
{ static_assert((... || overloads_type<Fs, T>), "alternative [T] matches no overload [Fs]"); };
template <typename F, typename... Fs>
using block_fn = overload<overload<F>, Fs...>;
template <typename T, typename U>
using copy_ref = std::conditional_t<std::is_lvalue_reference_v<T>, U&, U&&>;
template <typename... Fs, typename... Ts, typename V>
consteval void match_impl(std::variant<Ts...>, V&&) noexcept(noexcept(
(..., check_overload_set<block_fn<Fs, Fs...>, copy_ref<V, Ts>...>()),
(..., check_alternatives<copy_ref<V, Ts>, Fs...>())));
}
constexpr decltype(auto) match(auto&& v, auto&&... fs)
noexcept(noexcept((detail::match_impl<std::decay_t<decltype(fs)>...>(v, FWD(v)))))
{
if constexpr (sizeof...(fs) > 1)
{ return std::visit(overload{ FWD(fs)... }, FWD(v)); }
else
{ return std::visit(FWD(fs)..., FWD(v)); }
}
//
template <typename V, typename... Fs>
constexpr auto test_match(V&&, Fs&&...)
{
using O = overload<Fs...>;
static_assert(
std::invocable<decltype(match<V, Fs...>), V, Fs...> ==
std::invocable<decltype(std::visit<void, O, V>), O, V>);
}
struct foo
{
foo() { std::println("constructor"); }
~foo() { std::println("destructor"); }
foo(foo const&) { std::println("copy construct"); }
foo(foo&&) noexcept { std::println("move construct"); }
foo& operator=(foo const&) { std::println("copy assign"); return *this; }
foo& operator=(foo&&) noexcept { std::println("move assign"); return *this; }
char const* msg = "foo!";
};
struct A
{ char const* msg = "Hello World!"; };
struct B : A
{};
int main()
{
match(std::variant<B>{ B{} }
,[&](std::derived_from<A> auto&& in) { std::println("{}", in.msg); }
);
match(std::variant<double, int>{ 1 }
,[&](double) {}
,[&](int) {}
);
match(std::variant<int, long, unsigned>{ 1 }
,[&](std::integral auto&&) {}
,[&](int) {}
//,[&](double) {}
//,[&](std::floating_point auto) {}
);
auto f = foo{};
std::println("0"); match(std::variant<foo>{ foo{} },[&](foo&&) {});
std::println("1"); match(std::variant<foo>{ foo{} },[&](foo const&) {});
std::println("2"); match(std::variant<foo>{ foo{} },[&](foo) {});
std::println("3"); match(std::variant<foo const>{ foo{} },[&](foo const&) {});
std::println("4"); match(std::variant<foo const>{ foo{} },[&](foo) {});
std::println("5"); { auto var = std::variant<foo>{ foo{} }; match(var,[&](foo const&) {}); }
std::println("6"); { auto var = std::variant<foo>{ foo{} }; match(var,[&](foo) {}); }
std::println("7"); { auto var = std::variant<foo const>{ foo{} }; match(var,[&](foo const&) {}); }
std::println("8"); { auto var = std::variant<foo const>{ foo{} }; match(var,[&](foo) {}); }
std::println("9"); { auto const var = std::variant<foo>{ foo{} }; match(var,[&](foo const&) {}); }
std::println("A"); { auto const var = std::variant<foo>{ foo{} }; match(var,[&](foo) {}); }
std::println("B"); { auto const var = std::variant<foo const>{ foo{} }; match(var,[&](foo const&) {}); }
std::println("C"); { auto const var = std::variant<foo const>{ foo{} }; match(var,[&](foo) {}); }
std::println("D"); match(std::variant<int>{ 1 },[&](int&&) { std::println("{}", f.msg); });
std::println("E"); match(std::variant<int>{ 1 },[=](int&&) { std::println("{}", f.msg); });
{ test_match(std::variant<int>{ 1 }, [](int){}); }
{ test_match(std::variant<int>{ 1 }, [](int&&){}); }
{ test_match(std::variant<int>{ 1 }, [](int const&){}); }
{ test_match(std::variant<int const>{ 1 }, [](int){}); }
{ test_match(std::variant<int const>{ 1 }, [](int const&){}); }
{ auto var = std::variant<int>{ 1 }; test_match(var, [](int){}); }
{ auto var = std::variant<int>{ 1 }; test_match(var, [](int&){}); }
{ auto var = std::variant<int>{ 1 }; test_match(var, [](int const&){}); }
{ auto var = std::variant<int const>{ 1 }; test_match(var, [](int){}); }
{ auto var = std::variant<int const>{ 1 }; test_match(var, [](int const&){}); }
{ auto const var = std::variant<int>{ 1 }; test_match(var, [](int){}); }
{ auto const var = std::variant<int>{ 1 }; test_match(var, [](int const&){}); }
{ auto const var = std::variant<int const>{ 1 }; test_match(var, [](int){}); }
{ auto const var = std::variant<int const>{ 1 }; test_match(var, [](int const&){}); }
{ test_match(std::variant<int>{ 1 }, [](auto){}); }
{ test_match(std::variant<int>{ 1 }, [](auto&&){}); }
{ test_match(std::variant<int>{ 1 }, [](auto const&){}); }
{ test_match(std::variant<int const>{ 1 }, [](auto){}); }
{ test_match(std::variant<int const>{ 1 }, [](auto&){}); }
{ test_match(std::variant<int const>{ 1 }, [](auto&&){}); }
{ test_match(std::variant<int const>{ 1 }, [](auto const&){}); }
{ auto var = std::variant<int>{ 1 }; test_match(var, [](auto){}); }
{ auto var = std::variant<int>{ 1 }; test_match(var, [](auto&){}); }
{ auto var = std::variant<int>{ 1 }; test_match(var, [](auto&&){}); }
{ auto var = std::variant<int>{ 1 }; test_match(var, [](auto const&){}); }
{ auto var = std::variant<int const>{ 1 }; test_match(var, [](auto){}); }
{ auto var = std::variant<int const>{ 1 }; test_match(var, [](auto&){}); }
{ auto var = std::variant<int const>{ 1 }; test_match(var, [](auto&&){}); }
{ auto var = std::variant<int const>{ 1 }; test_match(var, [](auto const&){}); }
{ auto const var = std::variant<int>{ 1 }; test_match(var, [](auto){}); }
{ auto const var = std::variant<int>{ 1 }; test_match(var, [](auto&){}); }
{ auto const var = std::variant<int>{ 1 }; test_match(var, [](auto&&){}); }
{ auto const var = std::variant<int>{ 1 }; test_match(var, [](auto const&){}); }
{ auto const var = std::variant<int const>{ 1 }; test_match(var, [](auto){}); }
{ auto const var = std::variant<int const>{ 1 }; test_match(var, [](auto&){}); }
{ auto const var = std::variant<int const>{ 1 }; test_match(var, [](auto&&){}); }
{ auto const var = std::variant<int const>{ 1 }; test_match(var, [](auto const&){}); }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment