Skip to content

Instantly share code, notes, and snippets.

@willkill07
Last active September 15, 2023 03:49
Show Gist options
  • Save willkill07/7096868795064a9385589d1bdc90746d to your computer and use it in GitHub Desktop.
Save willkill07/7096868795064a9385589d1bdc90746d to your computer and use it in GitHub Desktop.
Runtime Dispatch of compile-time product
#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