C++ Transform a std::tuple to a std::vector or std::deque

前端 未结 2 1575
不思量自难忘°
不思量自难忘° 2021-02-04 15:57

In the simple parser library I am writing, the results of multiple parsers is combined using std::tuple_cat. But when applying a parser that returns the same result

相关标签:
2条回答
  • 2021-02-04 16:13

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

    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 is implementable in C++14 (see link for possible implementation). As an improvement, you could add either SFINAE or a static_assert that all the types in the Tuple are actually T.


    As T.C. points out, this incurs an extra copy of every element, since std::initializer_list is backed by a const array. That's unfortunate. We win some on not having to do boundary checks on every element, but lose some on the copying. The copying ends up being 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. Note that I dropped the leading 0 since we know the pack is non-empty. With C++17, this becomes cleaner with a fold-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 nice as the initializer_list constructor. Unfortunate.

    0 讨论(0)
  • 2021-02-04 16:14

    Here's one way to do it:

    #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;
    }
    
    0 讨论(0)
提交回复
热议问题