Skip to content

Instantly share code, notes, and snippets.

@obidavis
Last active May 29, 2023 15:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save obidavis/a65e8af309299fc5ebe13f0848d2f398 to your computer and use it in GitHub Desktop.
Save obidavis/a65e8af309299fc5ebe13f0848d2f398 to your computer and use it in GitHub Desktop.
Construct a variant alternative from a runtime index. Uses a static constexpr table to promote runtime integers to std::integral_constants, and forwards these along with args to the variant alternative's constructor. Useful for deserialisation? TODO: bounds checks, how to handle invalid indices...
#pragma once
#include <utility>
#include <variant>
/**
* @brief Gives the template `B` specialised with
* the parameter pack of the specialised template `A`.
* e.g. if `A` is a `std::tuple<int, float>` and `B` is
* a `std::variant`, `rebind_t<A, B>` will be a
* `std::variant<int, float>`.
*
* @tparam A The specialised template
* @tparam B The template to specialise with the parameters
* of A
*/
template<class A, template<class...> class B>
struct rebind;
template<template<class...> class A, class... T, template<class...> class B>
struct rebind<A<T...>, B> {
using type = B<T...>;
};
template<class A, template<class...> class B>
using rebind_t = typename rebind<A, B>::type;
// Same as above, but for non-type template parameters of type std::size_t
template<class A, template<std::size_t...> class B>
struct rebind_indices;
template<template<typename, std::size_t...> class A, typename Int, std::size_t... Is, template<std::size_t...> class B>
struct rebind_indices<A<Int, Is...>, B> {
using type = B<Is...>;
};
template<class A, template<std::size_t...> class B>
using rebind_indices_t = typename rebind_indices<A, B>::type;
template <std::size_t ...Is>
using index_variant_impl = std::variant<std::integral_constant<std::size_t, Is>...>;
template <std::size_t N>
using index_variant = rebind_indices_t<std::make_index_sequence<N>, index_variant_impl>;
/**
* @brief A wrapper around a static table of variants of types Ts.
* Each element in the table is a default constructed instance of the
* variant alternative at that element's index.
*
* @tparam Ts The types to instantiate the variants with
*/
template <typename ...Ts>
struct default_variant_table
{
constexpr static std::variant<Ts...> table[] = { Ts{}... };
constexpr static auto get(std::size_t index) { return table[index]; }
};
// https://stackoverflow.com/questions/16337610/how-to-know-if-a-type-is-a-specialization-of-stdvector
template<typename Test, template<typename...> class Ref>
struct is_specialization : std::false_type {};
template<template<typename...> class Ref, typename... Args>
struct is_specialization<Ref<Args...>, Ref>: std::true_type {};
/**
* @brief Constructs a variant with the alternative specified
* by the *runtime* index i and initializes the contained value
* with the arguments std::forward<Args>(args)....
* (cf. https://en.cppreference.com/w/cpp/utility/variant/variant (7))
*
* @tparam Variant The variant the alternative of which to construct
* @tparam Args Types of args
* @param index The variant alternative to construct
* @param args Arguments to pass to the constructor of
* the variant alternative
* @return Variant alternative constructed by args
*/
template <typename Variant, typename ...Args>
Variant construct_variant_alternative(std::size_t i, Args &&... args)
{
static_assert(is_specialization<Variant, std::variant>::value, "Must provide a variant");
using indices_table = rebind_t<
index_variant<std::variant_size_v<Variant>>,
default_variant_table>;
return std::visit(
[&args...](auto &&index) -> Variant
{
using index_constant = std::decay_t<decltype(index)>;
return Variant(std::in_place_index<index_constant::value>, std::forward<Args>(args)... );
},
indices_table::get(i)
);
}
// // Example usage
// #include <iostream>
// int main()
// {
// using v = std::variant<short, long>;
// std::size_t index;
// std::cout << "Enter index: ";
// std::cin >> index;
// index %= std::variant_size<v>::value;
// auto x = construct_variant_alternative<v>(index, 1'000'000);
// // prints 1000000 or 16960
// std::visit([](auto arg)
// {
// std::cout << "Value has width of " << sizeof(arg) << " bytes ";
// std::cout << "and value of " << arg << "\n";
// }, x);
// return 0;
// }
// Assembly comparision with switch alternative:
// https://godbolt.org/z/fs94jME9T
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment