【问题标题】:Initialize std::array with a range (pair of iterators)用一个范围(一对迭代器)初始化 std::array
【发布时间】:2012-06-07 09:32:47
【问题描述】:

如何从一个范围(由一对迭代器定义)初始化std::array

类似这样的:

vector<T> v;
...
// I know v has exactly N elements (e.g. I just called v.resize(N))
// Now I want a initialized with those elements
array<T, N> a(???);  // what to put here?

我以为array 会有一个构造函数采用一对迭代器,这样我就可以做array&lt;T, N&gt; a(v.begin(), v.end()),但它似乎根本没有构造函数!

我知道我可以 copy 将向量放入数组中,但我宁愿直接使用向量内容初始化数组,而不是先默认构造它。我该怎么办?

【问题讨论】:

  • 这种偏好有什么原因吗?性能几乎完全相同,因为默认构造函数(通常)仅分配您需要的基本结构。不会有额外的分配、复制或释放。
  • @DavidSchwartz:也许我的类中有一个 const 数组成员,所以我需要在初始化列表而不是构造函数主体中初始化它?
  • ---我们可以限制自己使用随机访问迭代器吗?如果是这样,我有某种解决方案--- 没关系,没有办法在编译时获取 size
  • @R.MartinhoFernandes:您可以假设在编译时大小为 N - 与 array 的模板参数出现的 N 相同。
  • 其实可以用输入迭代器来完成!

标签: c++ arrays c++11 stdvector range


【解决方案1】:

使用随机访问迭代器,并在编译时假设某个大小,您可以使用 pack of indices 来这样做:

template <std::size_t... Indices>
struct indices {
    using next = indices<Indices..., sizeof...(Indices)>;
};
template <std::size_t N>
struct build_indices {
    using type = typename build_indices<N-1>::type::next;
};
template <>
struct build_indices<0> {
    using type = indices<>;
};
template <std::size_t N>
using BuildIndices = typename build_indices<N>::type;

template <typename Iterator>
using ValueType = typename std::iterator_traits<Iterator>::value_type;

// internal overload with indices tag
template <std::size_t... I, typename RandomAccessIterator,
          typename Array = std::array<ValueType<RandomAccessIterator>, sizeof...(I)>>
Array make_array(RandomAccessIterator first, indices<I...>) {
    return Array { { first[I]... } };
}

// externally visible interface
template <std::size_t N, typename RandomAccessIterator>
std::array<ValueType<RandomAccessIterator>, N>
make_array(RandomAccessIterator first, RandomAccessIterator last) {
    // last is not relevant if we're assuming the size is N
    // I'll assert it is correct anyway
    assert(last - first == N); 
    return make_array(first, BuildIndices<N> {});
}

// usage
auto a = make_array<N>(v.begin(), v.end());

这假定编译器能够删除中间副本。我认为这个假设并不是一个很大的延伸。

实际上,它也可以使用输入迭代器来完成,因为花括号初始化列表中每个元素的计算在下一个元素的计算之前进行排序(第 8.5.4/4 节)。

// internal overload with indices tag
template <std::size_t... I, typename InputIterator,
          typename Array = std::array<ValueType<InputIterator>, sizeof...(I)>>
Array make_array(InputIterator first, indices<I...>) {
    return Array { { (void(I), *first++)... } };
}    

由于*first++ 中没有任何I,我们需要一个虚拟I 来引发包扩展。逗号操作符起到救援作用,void() 用于消除有关缺乏效果的警告,并防止逗号过载。

【讨论】:

  • +1。好的。 (但这次我讨厌这种语法:template&lt;typename&lt;Xyz&lt;template&lt;typename&lt;Abc...&gt;&gt;&gt;&gt;&gt;。我的意思是 WTF。)
  • 澄清一下,BuildIndices 是链接页面上的build_indices 模板吗?您能否将其包含在此处以使此答案独立?
  • 查看我使用 Boost 的解决方案。
  • @HighCommander4 FWIW,clang 也支持它们 :) 我认为别名模板被严重低估了。几个月前我开始使用它们,没有它们我就活不下去了:)
  • @HighCommander4 啊,这很简单。如果您将其转换为void,您就是在告诉 GCC 没有效果是您想要的。我更新了我的答案。
【解决方案2】:

如您所见,std::array 根本没有构造函数(编译器生成的默认构造函数除外)。

这是故意的,所以它可以像 C 数组一样被静态初始化。如果您想在没有静态初始化程序的情况下填充数组,则必须复制数据。

【讨论】:

    【解决方案3】:

    您可以将BOOST_PP_ENUM 用作:

    include <boost/preprocessor/repetition/enum.hpp>
    
    #define INIT(z, i, v) v[i] 
    
    std::vector<int> v;
    
    //fill v with at least 5 items 
    
    std::array<int,5> a = { BOOST_PP_ENUM(5, INIT, v) };  //MAGIC
    

    这里,最后一行扩展为:

    std::array<int,5> a = {v[0], v[1], v[2], v[3], v[4]}; //EXPANDED LINE
    

    这就是你想要的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-18
      • 1970-01-01
      • 2021-08-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多