【问题标题】:C++ Transform a std::tuple<A, A, A...> to a std::vector or std::dequeC++ 将 std::tuple<A, A, A...> 转换为 std::vector 或 std::deque
【发布时间】:2017-02-27 20:02:00
【问题描述】:

在我正在编写的简单解析器库中,多个解析器的结果使用std::tuple_cat进行组合。但是,当应用多次返回相同结果的解析器时,将这个元组转换为容器(如向量或双端队列)就变得很重要。

如何做到这一点?如何将std::tuple&lt;A&gt;std::tuple&lt;A, A&gt;std::tuple&lt;A, A, A&gt; 等类型的元组转换为std::vector&lt;A&gt;

我认为这可能使用typename ...Assizeof ...(As) 来实现,但我不确定如何创建一个更小的元组来递归调用该函数。或者如何编写一个从元组中逐个提取元素的迭代解决方案。 (因为std::get&lt;n&gt;(tuple) 是在编译时构造的)。

如何做到这一点?

【问题讨论】:

标签: c++ tuples c++14


【解决方案1】:

有了std::apply()的介绍,这就很直接了:

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() 是一个 C++17 函数,但可以在 C++14 中实现(请参阅链接了解可能的实现)。作为改进,您可以添加 SFINAE 或 static_assert,以使元组中的所有类型实际上都是 T


正如T.C. 指出的那样,这会导致每个元素的额外副本,因为std::initializer_listconst 数组支持。那真不幸。我们在不必对每个元素都进行边界检查的情况下赢得了一些胜利,但在复制时却失去了一些。复制最终过于昂贵,另一种实现方式是:

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));
}

请参阅this answer 了解扩展器技巧的说明。请注意,我放弃了领先的0,因为我们知道包是非空的。在 C++17 中,使用折叠表达式会变得更简洁:

    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));

虽然仍然相对不如initializer_list 构造函数好。不幸的。

【讨论】:

  • Plus 1 不仅是为了得到很好的答案,而且主要是为了断言建议。
  • 我唯一不喜欢的是初始化列表,它是每个元素一个额外的副本。
  • @T.C.哦,你是对的。我没有想到。真不幸……我猜有reserve()/push_back()
  • @T.C.添加了一个非初始化列表答案。我现在对此很不满意:(
  • 没有真正需要进行void 演员表,除非你是额外的偏执 - 在这种情况下,你忘了在折叠盒中进行演员表。一种中间方法可能是使用显式的T 数组和move_iterator
【解决方案2】:

这是一种方法:

#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】:

    虽然这并不能完全回答问题,但在某些情况下这仍然可能是合适的。仅当元组中的元素数量在 5、6 左右时。(并且您知道大小)。

    tuple<int, int, int, int> a = make_tuple(1, 2, 3, 4);
    auto [p, q, r, s] = a;
    vector<int> arr(p, q, r, s); // Now, arr has the same elements as in tuple a
    

    请注意,这是 C++ 17 功能。更多信息here

    【讨论】:

    • 变量pqrs“可能”被浪费了。您还应该将 init-list 用于向量而不是括号。
    猜你喜欢
    • 2020-06-20
    • 1970-01-01
    • 2014-06-20
    • 1970-01-01
    • 1970-01-01
    • 2012-05-23
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    相关资源
    最近更新 更多