vector of std::variant types to std::tuple

943 Views Asked by At

I am working on vector of std::variant types. Is there a way to convert it to std::tuple of the values holded by given std::variants ?

typedef std::variant<type1, type2,...> a_union;
std::vector<a_union> vec;

For example, I would like to have tuple like:

std::tuple<typeX, typeY, typeX,...>

Where members are types holded by consecutive variants in vector.

2

There are 2 best solutions below

0
On

This might be a solution for you, it uses optional and returns nullopt if the vector has incorrect values

#include <optional>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>
 
template<typename ... T, size_t ... Index>
std::optional<std::tuple<T...>> to_tuple(const std::vector<std::variant<T...>> & vec,
                                         std::index_sequence<Index...>)
{
    if (sizeof ... (T) != vec.size())
        return std::nullopt;
        
    if (not (...&& std::holds_alternative<T>(vec[Index])))
        return std::nullopt;
    return std::tuple<T...>(std::get<T>(vec[Index])...);
}

template<typename ... T>
std::optional<std::tuple<T...>> to_tuple(const std::vector<std::variant<T...>>& vec)
{
    return to_tuple(vec, std::index_sequence_for<T...>{});
}
0
On

The comments accurately state that this is probably an XY problem - a tuple requires compile-time information about the types of the data at each index that a vector of variants does not.

But, if you're willing to provide that information at the callsite, it's pretty straightforward to use parameter pack expansion to map a list of types to a string of calls to std::get<>.

You can provide that list of types by assuming the order of types in the variant to be the desired variant types at each index, as jo-art 's answer does. Here is a way to do by just providing a list of types you expect the tuple the vector to contain, in case they are different:

template<typename... Ts, typename Container, std::size_t... Is>
auto vector_to_tuple_impl(Container&& items, std::index_sequence<Is...>)
{
    return std::make_tuple(std::get<Ts>(items[Is])...);
}

template <typename... Ts, typename Container>
std::tuple<Ts...> vector_to_tuple(Container&& items)
{
    return vector_to_tuple_impl<Ts...>(items, std::index_sequence_for<Ts...>{});
}

(there's no error handling here, it will throw an std::bad_variant_access if you get the types wrong, and undefined behavior if you extract more elements than exist)

It's the same basic strategy: Use std::index_sequence_for to turn a parameter pack into an expandable parameter pack of container indices (0, 1, 2, etc...). The integer sequence pack and the type pack are expanded together to get the item at each index, and call std::get to extract the value.

usage:

    using SimpleVariant = std::variant<std::string_view, int>;
    std::vector<SimpleVariant> some_list { "hello", 42, "goodbye" };
    auto as_tuple = vector_to_tuple<std::string_view, int, std::string_view>(some_list);

proof of concept: https://godbolt.org/z/cGEW5s