Search code examples
c++variadic-templatesvariant

runtime indexing with std::get


I'm trying to turn a vector of variants into a tuple of vectors variadicly (i.e. I'd like to factor out the following code into a variadic template std::variant<Ts...>).

std::vector<std::variant<float, int>> vector_of_variants;
vector_of_variants.emplace_back(1);
vector_of_variants.emplace_back(2.f);

std::tuple<std::vector<float>, std::vector<int>> result;
for (auto& el : vector_of_variants)
{
    auto index = el.index();
    std::get<index>(result).pushback(std::get<index>(el)); // error: index is runtime value
}

However, the line std::get<index>(result).pushback(std::get<index>(el)); obviously won't work and I need to replace it with some std::visit like behaviour (i.e. generate the line tuple_size times and delegate at runtime).

What's the ergonomic way to do this?


Thanks to user17732522 I managed to get my template working https://godbolt.org/z/Geq1Kvjx9


Solution

  • Assuming you have guaranteed that corresponding indices in the variants and result match, something like the following should work for a naive implementation. Might not be the best performance:

    [&]<std::size_t... Is>(std::index_sequence<Is...>){
        ((index == Is ? std::get<Is>(result).push_back(std::get<Is>(el)) : void()), ...);
    }(std::make_index_sequence<std::tuple_size_v<decltype(result)>>{});
    

    You should also probably add a check for index == std::variant_npos in case the variant is in the invalid state. The above will ignore a variant in this state.


    If you don't care about repeated types, you can also use std::visit instead:

    std::visit([]<typename V>(V&& v){
        std::get<std::vector<std::remove_cvref_t<V>>>(result).push_back(std::forward<V>(v));
    }, el);
    

    This will throw if the variant is in the invalid state as std::visit always does. It also doesn't support const/volatile-qualified types properly, but these are unusual in a vector or variant.

    Unfortunately there is no std::visit alternative that passes both the value of the variant as well as the type index (e.g. as a std::integral_constant) to the visitor. That would make it easier here if repeated types need to be distinguished.