Last active
September 15, 2023 03:49
-
-
Save willkill07/7096868795064a9385589d1bdc90746d to your computer and use it in GitHub Desktop.
Runtime Dispatch of compile-time product
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 | |
#include <algorithm> | |
#include <array> | |
#include <concepts> | |
#include <optional> | |
#include <string_view> | |
#include <type_traits> | |
#include <utility> | |
// NOLINTBEGIN(readability-identifier-naming) | |
template<typename... Ts> | |
struct typelist{}; | |
template<size_t N> | |
struct fixed_string : public std::array<char, N> { | |
using fixed_string_tag = void; | |
constexpr fixed_string(const char (&str)[N]) noexcept { // NOLINT(*avoid-c-arrays) | |
std::copy_n(std::begin(str), N, this->begin()); | |
} | |
explicit constexpr operator std::string_view() const noexcept { | |
return std::string_view{this->data(), N - 1}; | |
} | |
}; | |
template<size_t N> | |
using fs = fixed_string<N>; | |
template<auto S> | |
concept is_fixed_string = std::same_as<void, typename decltype(S)::fixed_string_tag>; | |
template<fixed_string S> | |
consteval auto operator"" _fs() noexcept { | |
return S; | |
} | |
template<typename T, auto Value> | |
struct option_map { | |
using type = T; | |
using value_type = std::conditional_t<is_fixed_string<Value>, std::string_view, decltype(Value)>; | |
static constexpr value_type value{Value}; | |
}; | |
template<auto V> | |
using option_val = option_map<std::integral_constant<decltype(V), V>, V>; | |
#define FWD(a) std::forward<decltype(a)>(a) // NOLINT(cppcoreguidelines-macro-usage) | |
namespace detail { | |
// Exists to improve compiler diagnostics by forcing it to emit the index of the pack under investigation when a check fails | |
template<unsigned Index, typename T> | |
using CurrentOption = T; | |
// Exists to improve compiler diagnostics by forcing it to emit the index of the pack under investigation when a check fails | |
template<unsigned Index, typename T> | |
using CurrentArg = T; | |
template<unsigned Index, typename... Options> | |
concept all_same_as = [] <typename First, typename... Rest> { | |
return (... && std::same_as<CurrentOption<Index, First>, Rest>); | |
}.template operator() <Options...>(); | |
template <unsigned Index, typename Arg, typename... Option> | |
concept is_comparable_to = requires (Arg arg, Option... opt) { | |
{ (... , (opt != arg)) }; | |
}; | |
/// \brief generate a runtime conditional selection of a compile time dispatch | |
/// | |
/// \tparam Index the index being analyzed | |
/// \tparam Return the return type | |
/// \tparam CurrOption the current option to compare against | |
/// \param fn the template lambda to ultimately invoke | |
/// \param options the remaining typelist of options to process | |
/// \param resolved the typelist of resolved arguments | |
/// \param arg the next runtime argument to compare against CurrOption | |
/// \param args the runtime arguments | |
/// \returns the return value if invoked else return std::nullopt | |
template<unsigned Index, typename Return, typename CurrOption, typename... Resolved> | |
[[gnu::always_inline, nodiscard]] constexpr std::optional<Return> | |
select_result(auto&& fn, auto&& options, typelist<Resolved...>, auto&& arg, auto&&... args) noexcept; | |
/// \brief perform dynamic dispatch based on the compile-time options provided and the run-time arguments passed | |
/// | |
/// \tparam Index the index being analyzed | |
/// \tparam Return the explicit, specified return type of the dispatch | |
/// \param fn the template lambda to ultimately invoke | |
/// \param options the typelist of remaining options | |
/// \param resolved the typelist of resolved options | |
/// \param args runtime arguments that first resolve dispatch, then forward the rest to the template lambda. | |
template<unsigned Index, typename Return, typename... CurrOption, typename... Options, typename CurrArg> | |
[[gnu::always_inline, nodiscard]] constexpr std::optional<Return> | |
peel_result(auto&& fn, typelist<typelist<CurrOption...>, Options...>, auto&& resolved, CurrArg&& arg, auto&&... args) noexcept | |
requires all_same_as<Index, typename CurrOption::value_type...> and is_comparable_to<Index, CurrArg, typename CurrOption::value_type...> { | |
std::optional<Return> ret{std::nullopt}; | |
(... || (ret = select_result<Index, Return, CurrOption>(FWD(fn), typelist<Options...>{}, FWD(resolved), FWD(arg), FWD(args)...)).has_value()); | |
return ret; | |
} | |
/// \brief perform dynamic dispatch based on the compile-time options provided and the run-time arguments passed | |
/// | |
/// \tparam Index the index being analyzed | |
/// \tparam Return the explicit, specified return type of the dispatch | |
/// \param fn the template lambda to ultimately invoke | |
/// \param options the typelist of remaining options | |
/// \param resolved the typelist of resolved options | |
/// \param args runtime arguments that first resolve dispatch, then forward the rest to the template lambda. | |
template<unsigned Index, typename Return, typename... Options, typename... Resolved> | |
[[gnu::always_inline, nodiscard]] constexpr std::optional<Return> | |
dispatcher_result(auto&& fn, typelist<Options...>&& options, typelist<Resolved...>&& resolved, auto&&... args) noexcept { | |
if constexpr (sizeof...(Options) != 0) { | |
return peel_result<Index, Return>(FWD(fn), std::move(options), std::move(resolved), FWD(args)...); | |
} else { | |
return fn.template operator()<Resolved...>(FWD(args)...); | |
} | |
} | |
template<unsigned Index, typename Return, typename CurrOption, typename... Resolved> | |
[[gnu::always_inline, nodiscard]] constexpr std::optional<Return> | |
select_result(auto&& fn, auto&& options, typelist<Resolved...>, auto&& arg, auto&&... args) noexcept { | |
if (CurrOption::value != arg) return std::nullopt; | |
return detail::dispatcher_result<Index + 1, Return>(FWD(fn), FWD(options), typelist<Resolved..., typename CurrOption::type>{}, FWD(args)...); | |
} | |
/// \brief generate a runtime conditional selection of a compile time dispatch | |
/// | |
/// \tparam Index the index being analyzed | |
/// \tparam CurrOption the current option to compare against | |
/// \param fn the template lambda to ultimately invoke | |
/// \param options the remaining typelist of options to process | |
/// \param resolved the typelist of resolved arguments | |
/// \param arg the runtime argument to compare against CurrOption | |
/// \param args the runtime arguments | |
/// \returns true if we dispatched (compile-time option matched runtime value) | |
template<unsigned Index, typename CurrOption, typename... Resolved, typename Arg> | |
[[gnu::always_inline, nodiscard]] constexpr bool | |
select(auto&& fn, auto&& options, [[maybe_unused]] typelist<Resolved...>, auto&& arg, auto&&... args) noexcept; | |
/// \brief perform dynamic dispatch based on the compile-time options provided and the run-time arguments passed | |
/// | |
/// \tparam Index the index being analyzed | |
/// \param fn the template lambda to ultimately invoke | |
/// \param options the typelist of remaining options | |
/// \param resolved the typelist of resolved options | |
/// \param args runtime arguments that first resolve dispatch, then forward the rest to the template lambda. | |
template<unsigned Index, typename... CurrOption, typename... Options, typename CurrArg> | |
[[gnu::always_inline, nodiscard]] constexpr bool | |
peel(auto&& fn, typelist<typelist<CurrOption...>, Options...>, auto&& resolved, CurrArg&& arg, auto&&... args) noexcept | |
requires all_same_as<Index, typename CurrOption::value_type...> and is_comparable_to<Index, CurrArg, typename CurrOption::value_type...> { | |
return (... || select<Index, CurrOption>(FWD(fn), typelist<Options...>{}, FWD(resolved), FWD(arg), FWD(args)...)); | |
} | |
/// \brief perform dynamic dispatch based on the compile-time options provided and the run-time arguments passed | |
/// | |
/// \tparam Index the index being analyzed | |
/// \param fn the template lambda to ultimately invoke | |
/// \param options the typelist of remaining options | |
/// \param resolved the typelist of resolved options | |
/// \param args runtime arguments that first resolve dispatch, then forward the rest to the template lambda. | |
template<unsigned Index, typename... Options, typename... Resolved> | |
[[gnu::always_inline, nodiscard]] constexpr bool | |
dispatcher(auto&& fn, typelist<Options...> options, typelist<Resolved...> resolved, auto&&... args) noexcept { | |
if constexpr (sizeof...(Options) != 0) { | |
return peel<Index>(FWD(fn), FWD(options), FWD(resolved), FWD(args)...); | |
} else { | |
fn.template operator()<Resolved...>(FWD(args)...); | |
return true; | |
} | |
} | |
template<unsigned Index, typename CurrOption, typename... Resolved> | |
[[gnu::always_inline, nodiscard]] constexpr bool | |
select(auto&& fn, auto&& options, [[maybe_unused]] typelist<Resolved...>, auto&& arg, auto&&... args) noexcept { | |
if (CurrOption::name != arg) return false; | |
using New = typename CurrOption::type; | |
detail::dispatcher<Index + 1>(FWD(fn), FWD(options), typelist<Resolved..., New>{}, FWD(args)...); | |
return true; | |
} | |
} // end namespace detail | |
/// \brief perform runtime-based dynamic dispatch on a compile-time generated | |
/// decision tree of comparisons. | |
/// | |
/// \tparam Return the return type to expect -- all specializations must yield | |
/// the same return type due to the runtime switch | |
/// \tparam Options a variadic list of option_map<Type, Val>-s | |
/// \param Fn the template lambda to invoke such that sizeof...(Options) is the | |
/// number of template arguments the lambda expects | |
/// \param Args the runtime arguments to forward. The first sizeof...(Options) | |
/// arguments will be converted to template arguments. Any remaining arguments | |
/// will be forwarded to the lambda. | |
/// \return the result of the invoked template lambda (or nullopt) | |
template<typename Return, typename... Options, typename Fn, typename... Args> // | |
[[nodiscard]] constexpr std::optional<Return> | |
dispatch_result(Fn&& fn, Args&&... args) noexcept { | |
return detail::dispatcher_result<0u, Return>(FWD(fn), typelist<Options...>{}, typelist<>{}, FWD(args)...); | |
} | |
/// \brief perform runtime-based dynamic dispatch on a compile-time generated | |
/// decision tree of comparisons. | |
/// | |
/// \tparam Options a variadic list of option_map<Type, Val>-s | |
/// \param Fn the template lambda to invoke such that sizeof...(Options) is the | |
/// number of template arguments the lambda expects | |
/// \param Args the runtime arguments to forward. The first sizeof...(Options) | |
/// arguments will be converted to template arguments. Any remaining arguments | |
/// will be forwarded to the lambda. | |
/// \returns true if the function was dispatched, false otherwise | |
template<typename... Options, typename Fn, typename... Args> // | |
[[nodiscard]] constexpr bool | |
dispatch(Fn&& fn, Args&&... args) noexcept { | |
return detail::dispatcher<0u>(FWD(fn), typelist<Options...>{}, typelist<>{}, FWD(args)...); | |
} | |
#undef FWD | |
// NOLINTEND(readability-identifier-naming) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment