【问题标题】:Use std::vector for std::array initialization使用 std::vector 进行 std::array 初始化
【发布时间】:2017-04-17 04:47:04
【问题描述】:

假设我有一个在编译时已知大小的std::vector,我想把它变成一个std::array。我该怎么做?是否有标准功能可以做到这一点?

到目前为止我最好的解决方案是:

template<class T, std::size_t N, class Indexable, std::size_t... Indices>
std::array<T, N> to_array_1(const Indexable& indexable, std::index_sequence<Indices...>) {
  return {{ indexable[Indices]... }};
}

template<class T, std::size_t N, class Indexable>
std::array<T, N> to_array(const Indexable& indexable) {
  return to_array_1<T, N>(indexable, std::make_index_sequence<N>());
}

std::array<Foo, 123> initFoos {
  std::vector<Foo> lst;
  for (unsigned i = 0; i < 123; ++i)
    lst.push_back(getNextFoo(lst));
  return to_array<Foo, 123>(lst); // passing lst.begin() works, too
}

该应用程序类似于Populate std::array with non-default-constructible type (no variadic templates):我也有一个不可默认构造的类型,所以我需要在数组初始化时计算实际值。然而,与这个问题相反,对我来说,这些值不仅是索引的函数,而且是前面值的函数。与一系列函数调用相比,我可以更轻松地使用循环来构建我的值。所以我在一个循环中构造元素并将它们放在一个向量中,然后我想使用该向量的最终状态来初始化数组。

以上似乎编译和工作正常,但也许有办法改进它。

  1. 也许我可以巧妙地利用一些我不知道的标准库功能。
  2. 也许我可以通过某种方式避免使用辅助函数。
  3. 也许我可以以某种方式将其表述为与元素的移动语义而不是上面使用的复制语义一起使用。
  4. 也许我可以使用operator[] 来避免随机访问,而只使用前向迭代器语义,这样它也适用于std::setstd::forward_list 作为输入。
  5. 也许我应该停止使用std::vector,而是使用std::array&lt;std::optional&lt;T&gt;, N&gt; 来表达我的目标,使用C++17 或其他等效实现。

相关问题:

【问题讨论】:

  • 有趣的问题,但这闻起来很糟糕。为什么要生成数据然后复制它们?这是由于您希望将它们打包在std::array 中,但与原始std::vector 相比,它有什么优势?两者都将数据保存在连续的内存中,因此前者应该没有后者的优势。唯一的区别是size 是一个编译时变量,但无论如何都是这样。
  • @Walter:有效问题。一方面,我的原件是std::set,所以无论如何我至少需要转换一次。我也在使用转换构造函数对元素执行类型转换。所以这不仅仅是保留std::vector 的问题。不过我可以用一个。反对的主要论据可能是我的感觉,即编译器知道的越多,它就可以优化得越好。我在实践中只处理 24 个元素,虽然这对于完整的循环展开来说可能太多了,但它可以例如展开四人一组,知道计数可以被 4 整除。只是一种感觉。

标签: c++ stl initialization c++14 stdarray


【解决方案1】:

我建议:

template<typename T, typename Iter, std::size_t... Is>
constexpr auto to_array(Iter& iter, std::index_sequence<Is...>)
-> std::array<T, sizeof...(Is)> {
    return {{ ((void)Is, *iter++)... }};
}

template<std::size_t N, typename Iter,
         typename T = typename std::iterator_traits<Iter>::value_type>
constexpr auto to_array(Iter iter)
-> std::array<T, N> {
    return to_array<T>(iter, std::make_index_sequence<N>{});
}

这会从迭代器中推断出元素类型,并将复制与移动语义留给调用者——如果调用者想要移动,他们可以通过std::move_iterator 或类似方式选择加入:

auto initFoos() {
    constexpr unsigned n{123};

    std::vector<Foo> lst;
    for (unsigned i{}; i != n; ++i) {
        lst.push_back(getNextFoo(lst));
    }

    // copy-init array elements
    return to_array<n>(lst.cbegin());

    // move-init array elements
    return to_array<n>(std::make_move_iterator(lst.begin()));
}

Online Demo


编辑:如果想要覆盖推导的元素类型,如 cmets 所示,那么我建议:

template<typename T, typename Iter, std::size_t... Is>
constexpr auto to_array(Iter& iter, std::index_sequence<Is...>)
-> std::array<T, sizeof...(Is)> {
    return {{ ((void)Is, T(*iter++))... }};
}

template<std::size_t N, typename U = void, typename Iter,
         typename V = typename std::iterator_traits<Iter>::value_type,
         typename T = std::conditional_t<std::is_same<U, void>{}, V, U>>
constexpr auto to_array(Iter iter)
-> std::array<T, N> {
    return to_array<T>(iter, std::make_index_sequence<N>{});
}

这使得元素类型是可选的,但使它成为第二个参数而不是第一个参数,所以用法看起来像to_array&lt;N, Bar&gt;(lst.begin()) 而不是to_array&lt;Bar, N&gt;(lst.begin())

【讨论】:

  • 多个iter++ 表达式的求值顺序是什么?
  • @MaximEgorushkin :括号初始化列表内的所有扩展都保证从左到右执行。
  • 谢谢!这解决了我列表中的第 3 点和第 4 点,而我并不知道移动迭代器。在我的实际应用程序中,我正在使用显式转换构造函数,从 vector&lt;Foo&gt; 初始化 array&lt;Bar,N&gt; 使用 Bar::Bar(const Foo&amp;) 并且可能在将来使用 Bar::Bar(Foo&amp;&amp;)。事实证明,您提供的代码不算作显式转换,而我的原始代码则算作显式转换。所以我在那里添加了一个T(…) 构造函数调用。
猜你喜欢
  • 2021-06-26
  • 1970-01-01
  • 1970-01-01
  • 2013-06-06
  • 2017-04-04
  • 1970-01-01
  • 1970-01-01
  • 2015-10-05
  • 2012-02-10
相关资源
最近更新 更多