Search code examples
c++templatesc++17template-meta-programmingvariant

Default constructing an std::variant from index


What's the easiest way to default construct an std::variant from the index of the desired type, when the index is only known at runtime? In other words, I want to write:

const auto indx = std::variant<types...>{someobject}.index();
//...somewhere later, indx having been passed around...
std::variant<types...> var = variant_from_index(indx);
///var is now set to contain a default constructed someobject

Note that indx cannot be made constexpr, so std::in_place_index doesn't work here.

The problem here is of course that since it isn't known which constructor from types... to call at compile time, somehow basically a table of all possible constructors (or maybe default constructed variants to copy from) has to be built at compile time and then accessed at run time. Some template magic is apparently in place here, but what would be the cleanest way?

I tried the following (on coliru), but the index sequence seems to come out wrong (the print in the end gives 2 0 0), and I'm confused as to why:

Edit: it works as fixed below, I had the constexpr array initialization wrong. So the question is now, is there a neater way to do this?

#include <variant>
#include <iostream>

using var_t = std::variant<int, float, const char *>;

//For debug
template<class ...types>
struct WhichType;

template<class T, class U>
struct default_variants;
template<class...Params, std::size_t... I>
struct default_variants<std::variant<Params...>, std::index_sequence<I...>> {
    using variant_t = std::variant<Params...>;
    //Uncomment to see the index sequence
    //WhichType<std::index_sequence<I...>> idx{};
    constexpr static variant_t variants[sizeof...(Params)]{variant_t{std::in_place_index<I>}...};
    constexpr static std::size_t indices[sizeof...(Params)]{I...};
};
template<class T>
struct default_variants_builder;
template<class...Params>
struct default_variants_builder<std::variant<Params...>> {
    using indices = std::make_index_sequence<sizeof...(Params)>;
    using type = default_variants<std::variant<Params...>, indices>;
};


int main() {
    using builder_t = typename default_variants_builder<var_t>::type;
    var_t floatvar{1.2f};
    var_t variant2 = builder_t::variants[floatvar.index()];
    std::cout << "Contained " << floatvar.index() << "; Now contains " << variant2.index() << "\n";
}

Solution

  • With Boost.Mp11 this is basically a one-liner (as always):

    template <typename V>
    auto variant_from_index(size_t index) -> V
    {
        return mp_with_index<mp_size<V>>(index,
            [](auto I){ return V(std::in_place_index<I>); });
    }
    

    Your description of the problem is accurate - you need a way to turn a runtime index into a compile-time index. mp_with_index does that for you - you give it the runtime index and the maximum compile-time index (mp_size<V> here, which would give the same value as std::variant_size_v<V> if you prefer that instead) and it will invoke a function you provide with the correct constant (I has type integral_constant<size_t, index> here, except with index being a constant expression).