使用 std::vector 进行 std::array 初始化
Use std::vector for std::array initialization
假设我有一个 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):我也有一个不是 default-constructible 的类型,因此我需要在数组初始化时计算实际值。然而与那个问题相反,对我来说,这些值不仅是指数的函数,而且是前面值的函数。与一系列函数调用相比,使用循环可以更轻松地构建我的值。所以我在一个循环中构建元素并将它们放在一个向量中,然后我想使用该向量的最终状态来初始化数组。
以上似乎可以编译并工作正常,但也许有改进的方法。
- 也许我可以巧妙地利用一些我不知道的标准库功能。
- 也许我能以某种方式避免辅助函数。
- 也许我可以用某种方式来表述它,使其与元素的移动语义一起工作,而不是上面使用的复制语义。
- 也许我可以避免使用
operator[]
进行随机访问,而是仅使用前向迭代器语义,这样它也适用于 std::set
或 std::forward_list
作为输入。
- 也许我应该停止使用
std::vector
而是使用 std::array<std::optional<T>, N>
来表达我的目标,使用 C++17 或一些等效的实现。
相关问题:
- Copy std::vector into std::array 它不假定没有默认构造函数的类型,因此在默认初始化后复制在那里是可行的,但对我来说不可行。
- Populate std::array with non-default-constructible type (no variadic templates) 它根据索引独立计算每个元素,因此它尽量避免使用中间容器。答案使用可变参数模板,即使标题要求避免它们。
我会提议:
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()));
}
编辑: 如果有人想覆盖推导的元素类型,如评论中所示,那么我建议:
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<N, Bar>(lst.begin())
而不是 to_array<Bar, N>(lst.begin())
。
假设我有一个 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):我也有一个不是 default-constructible 的类型,因此我需要在数组初始化时计算实际值。然而与那个问题相反,对我来说,这些值不仅是指数的函数,而且是前面值的函数。与一系列函数调用相比,使用循环可以更轻松地构建我的值。所以我在一个循环中构建元素并将它们放在一个向量中,然后我想使用该向量的最终状态来初始化数组。
以上似乎可以编译并工作正常,但也许有改进的方法。
- 也许我可以巧妙地利用一些我不知道的标准库功能。
- 也许我能以某种方式避免辅助函数。
- 也许我可以用某种方式来表述它,使其与元素的移动语义一起工作,而不是上面使用的复制语义。
- 也许我可以避免使用
operator[]
进行随机访问,而是仅使用前向迭代器语义,这样它也适用于std::set
或std::forward_list
作为输入。 - 也许我应该停止使用
std::vector
而是使用std::array<std::optional<T>, N>
来表达我的目标,使用 C++17 或一些等效的实现。
相关问题:
- Copy std::vector into std::array 它不假定没有默认构造函数的类型,因此在默认初始化后复制在那里是可行的,但对我来说不可行。
- Populate std::array with non-default-constructible type (no variadic templates) 它根据索引独立计算每个元素,因此它尽量避免使用中间容器。答案使用可变参数模板,即使标题要求避免它们。
我会提议:
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()));
}
编辑: 如果有人想覆盖推导的元素类型,如评论中所示,那么我建议:
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<N, Bar>(lst.begin())
而不是 to_array<Bar, N>(lst.begin())
。