Timo
Timo

Reputation: 871

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";
}

Upvotes: 5

Views: 3464

Answers (4)

Barry
Barry

Reputation: 303186

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).

Upvotes: 9

Quentin
Quentin

Reputation: 63134

How about this?

template <class Variant, std::size_t I = 0>
Variant variant_from_index(std::size_t index) {
    if constexpr(I >= std::variant_size_v<Variant>)
        throw std::runtime_error{"Variant index " + std::to_string(I + index) + " out of bounds"};
    else
        return index == 0
            ? Variant{std::in_place_index<I>}
            : variant_from_index<Variant, I + 1>(index - 1);
}

See it live on Wandbox

Upvotes: 8

einpoklum
einpoklum

Reputation: 131666

I believe a (somewhat) elegant way might be using a more general idiom for choosing a numeric template parameter value at run time, as discussed in this question:

Idiom for simulating run-time numeric template parameters?

The foo function there will be std::get<std::size_t I> (or a lambda which captures the variant and takes no arguments).

Upvotes: 1

javidcf
javidcf

Reputation: 59711

Not sure if this is very elegant or not but I think it works:

#include <variant>
#include <iostream>

template<typename V, std::size_t N = std::variant_size_v<V>>
struct variant_by_index {
    V make_default(std::size_t i) {
        if (i >= std::variant_size_v<V>) {
            throw std::invalid_argument("bad type index.");
        }
        constexpr size_t index = std::variant_size_v<V> - N;
        if (i == index) {
            return std::variant_alternative_t<index, V>();
        } else {
            return variant_by_index<V, N - 1>().make_default(i);
        }
    }
};

template<typename V>
struct variant_by_index<V, 0> {
    V make_default(std::size_t i) {
        throw std::bad_variant_access("bad type index.");
    }
};

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

int main() {
    variant_by_index<var_t> type_indexer;
    var_t my_var_0 = type_indexer.make_default(0);
    std::cout << "my_var_0 has int? " << std::holds_alternative<int>(my_var_0) <<  "\n";
    var_t my_var_1 = type_indexer.make_default(1);
    std::cout << "my_var_1 has float? " << std::holds_alternative<float>(my_var_1) <<  "\n";
    try {
        var_t my_var_1 = type_indexer.make_default(3);
    } catch(const std::bad_variant_access&) {
       std::cout << "Could not create with type 3.\n";
    }
    return 0;
}

Upvotes: 2

Related Questions