Last active
June 13, 2016 02:25
-
-
Save Quuxplusone/3f7100f72551f6a7028844b298c43508 to your computer and use it in GitHub Desktop.
Variadic switch statements; compare https://gist.github.com/ecatmur/7f5f8b44e70f414742e4eae1efdd0ca7
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#ifndef H_SWITCH_ON | |
#define H_SWITCH_ON | |
#include <stdexcept> | |
#include <tuple> | |
#include <type_traits> | |
#include <utility> | |
namespace xstd { | |
class bad_switch_case : public std::exception { | |
public: | |
bad_switch_case() = default; | |
virtual const char *what() const noexcept override { return "no matching case in switch"; } | |
}; | |
template<typename Condition, typename Action> | |
class switch_generic_case { | |
std::tuple<Condition, Action> ca; | |
public: | |
template<typename C, typename A> | |
constexpr switch_generic_case(C&& c, A&& a) : ca(std::forward<C>(c), std::forward<A>(a)) {} | |
constexpr const Action& action() const { return std::get<1>(ca); } | |
template<typename U> | |
constexpr bool accepts(U&& u) const { | |
return std::get<0>(ca)(std::forward<U>(u)); | |
} | |
}; | |
template<typename C, typename A> | |
constexpr auto make_switch_generic_case(const C&& c, const A&& a) | |
{ | |
using Condition = std::decay_t<C>; | |
using Action = std::decay_t<A>; | |
return switch_generic_case<Condition, Action>(c, a); | |
} | |
template<typename V, typename A> | |
constexpr auto make_switch_case(V&& v, A&& a) | |
{ | |
return make_switch_generic_case( | |
[value = std::forward<V>(v)](auto&& x){ return std::forward<decltype(x)>(x) == value; }, | |
std::forward<A>(a) | |
); | |
} | |
template<typename A> | |
constexpr auto make_switch_default(A&& a) | |
{ | |
return make_switch_generic_case( | |
[](auto&&) { return true; }, | |
std::forward<A>(a) | |
); | |
} | |
template<typename... Cases> | |
class detail_switch_; | |
template<> | |
class detail_switch_<> { | |
public: | |
detail_switch_() = default; | |
template<typename U> void on(U&& u) { throw bad_switch_case(); } | |
}; | |
template<typename... Conditions, typename... Actions> | |
class detail_switch_<switch_generic_case<Conditions, Actions>...> { | |
std::tuple<switch_generic_case<Conditions, Actions>...> cases; | |
template<typename U, size_t I> | |
constexpr auto detail_on_(U&& u, std::index_sequence<I>, int) const { | |
const auto& caseI = std::get<I>(cases); | |
if (caseI.accepts(std::forward<U>(u))) { | |
return caseI.action()(); | |
} else { | |
throw bad_switch_case(); | |
} | |
} | |
template<typename U, size_t I, size_t J, size_t K, size_t... Is> | |
constexpr auto detail_on_(U&& u, std::index_sequence<I, J, K, Is...>, ...) const { | |
const auto& caseI = std::get<I>(cases); | |
const auto& caseJ = std::get<J>(cases); | |
const auto& caseK = std::get<K>(cases); | |
if (caseI.accepts(std::forward<U>(u))) { | |
return caseI.action()(); | |
} else if (caseJ.accepts(std::forward<U>(u))) { | |
return caseJ.action()(); | |
} else { | |
return detail_on_(std::forward<U>(u), std::index_sequence<K, Is...>{}, 0); | |
} | |
} | |
template<typename U, size_t I, size_t... Is> | |
constexpr auto detail_on_(U&& u, std::index_sequence<I, Is...>, ...) const { | |
const auto& caseI = std::get<I>(cases); | |
if (caseI.accepts(std::forward<U>(u))) { | |
return caseI.action()(); | |
} else { | |
return detail_on_(std::forward<U>(u), std::index_sequence<Is...>{}, 0); | |
} | |
} | |
public: | |
template<typename... Cs> | |
constexpr detail_switch_(Cs&&... cs) : cases(std::forward<Cs>(cs)...) {} | |
template<typename U> | |
constexpr auto on(U&& u) const { | |
return this->detail_on_( | |
std::forward<U>(u), | |
std::make_index_sequence<sizeof...(Actions)>{}, | |
0 | |
); | |
} | |
}; | |
template<typename... Cs> | |
constexpr auto make_switch(const Cs&&... cases) | |
{ | |
return detail_switch_<std::decay_t<Cs>...>(cases...); | |
} | |
template<typename V, typename... Cs> | |
constexpr auto switch_on(V&& v, Cs&&... cases) | |
{ | |
return make_switch(std::forward<Cs>(cases)...).on(std::forward<V>(v)); | |
} | |
} // namespace xstd | |
#ifdef TESTING | |
template<class... V> | |
struct VariantPtr { | |
std::size_t idx; | |
void* data; | |
}; | |
template<class... V, class F> | |
auto apply(VariantPtr<V...> p, F&& f) { | |
return detail_apply_(p, std::forward<F>(f), std::make_index_sequence<sizeof...(V)>{}); | |
} | |
template<class... V, class F, size_t... Is> | |
auto detail_apply_(VariantPtr<V...> p, F&& f, std::index_sequence<Is...>) { | |
return xstd::switch_on(p.idx, | |
xstd::make_switch_case(Is, [&](){ | |
using elt_type = std::tuple_element_t<Is, std::tuple<V...>>; | |
return std::forward<F>(f)(static_cast<elt_type*>(p.data)); | |
})... | |
); | |
} | |
struct Fake { template<typename T> auto operator()(T *x) const { *x = -*x; } }; | |
template auto apply(VariantPtr<int,float,char,double,long,long long,unsigned,unsigned long>, Fake&&); | |
#endif // TESTING | |
#endif // H_SWITCH_ON |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment