Reputation: 63
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.
Upvotes: 1
Views: 1117
Reputation: 4079
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
Upvotes: 0
Reputation: 215
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...>{});
}
Upvotes: 3