C ++ Convert std :: tuple <A, A, A ...> to std :: vector or std :: deque

In a simple parser library that I write, the results of several parsers are combined using std::tuple_cat . But when using a parser that returns the same result several times, it becomes important to convert this tuple into a container, such as a vector or deck.

How can I do that? Like any tuple like std::tuple<A> , std::tuple<A, A> , std::tuple<A, A, A> , etc. Converts to std::vector<A> ?

I think this is possible with typename ...As and sizeof ...(As) , but I'm not sure how to create a smaller tuple to call the function recursively. Or how to write an iterative solution that extracts elements from a tuple one at a time. (since std::get<n>(tuple) is created at compile time).

How to do it?

+6
source share
2 answers

With the introduction of std::apply() this is very simple:

 template <class Tuple, class T = std::decay_t<std::tuple_element_t<0, std::decay_t<Tuple>>>> std::vector<T> to_vector(Tuple&& tuple) { return std::apply([](auto&&... elems){ return std::vector<T>{std::forward<decltype(elems)>(elems)...}; }, std::forward<Tuple>(tuple)); } 

std::apply() is a C ++ 17 function, but implemented in C ++ 14 (see the link for a possible implementation). As an improvement, you can add either SFINAE or static_assert that all types in Tuple are actually T


As TC points out, this takes an extra copy of each element, since std::initializer_list supported by the const array. This is unfortunate. We win that you do not need to perform border checks on each element, but lose some when copying. Copying ends too expensive, an alternative implementation would be:

 template <class Tuple, class T = std::decay_t<std::tuple_element_t<0, std::decay_t<Tuple>>>> std::vector<T> to_vector(Tuple&& tuple) { return std::apply([](auto&&... elems) { using expander = int[]; std::vector<T> result; result.reserve(sizeof...(elems)); expander{(void( result.push_back(std::forward<decltype(elems)>(elems)) ), 0)...}; return result; }, std::forward<Tuple>(tuple)); } 

See this answer for an explanation of the expander trick. Please note that I lost the leading 0 , since we know that the package is not empty. With C ++ 17, this gets cleaner with a flash expression:

  return std::apply([](auto&&... elems) { std::vector<T> result; result.reserve(sizeof...(elems)); (result.push_back(std::forward<decltype(elems)>(elems)), ...); return result; }, std::forward<Tuple>(tuple)); 

Although still relatively not as good as the initializer_list constructor. Unhappy.

+12
source

Here is one way to do this:

 #include <tuple> #include <algorithm> #include <vector> #include <iostream> template<typename first_type, typename tuple_type, size_t ...index> auto to_vector_helper(const tuple_type &t, std::index_sequence<index...>) { return std::vector<first_type>{ std::get<index>(t)... }; } template<typename first_type, typename ...others> auto to_vector(const std::tuple<first_type, others...> &t) { typedef typename std::remove_reference<decltype(t)>::type tuple_type; constexpr auto s = std::tuple_size<tuple_type>::value; return to_vector_helper<first_type, tuple_type> (t, std::make_index_sequence<s>{}); } int main() { std::tuple<int, int> t{2,3}; std::vector<int> v=to_vector(t); std::cout << v[0] << ' ' << v[1] << ' ' << v.size() << std::endl; return 0; } 
+3
source

Source: https://habr.com/ru/post/1015318/


All Articles