如何为一维数组实现的多维数组添加嵌套初始化(支持不可移动类型)
解决一维底层多维数组的嵌套初始化问题(兼容不可移动类型)
我明白你的需求:既要保留一维std::array作为底层容器,又要支持嵌套初始化,同时不能依赖元素的移动/复制构造函数——这确实需要巧妙的模板技巧来实现,核心是让prvalue直接初始化到底层数组的对应位置,完全绕开移动操作。
问题回顾
你当前的multi_array通过聚合初始化支持扁平化构造,但嵌套初始化会报错,因为编译器无法将嵌套的初始化列表匹配到一维数组的聚合规则上。而直接添加构造函数又容易触发移动操作,无法兼容non_movable这类类型。
解决方案:递归索引定位 + 直接初始化
我们可以通过递归的元素定位逻辑,结合std::index_sequence展开,把嵌套初始化列表中的元素直接映射到底层一维数组的对应位置,全程不需要移动或复制元素。
完整实现代码
#include <array> #include <cstddef> #include <utility> #include <tuple> struct non_movable { explicit non_movable(int) {} non_movable(const non_movable&) = delete; non_movable(non_movable&&) = delete; non_movable& operator=(const non_movable&) = delete; non_movable& operator=(non_movable&&) = delete; ~non_movable() = default; }; namespace detail { // 计算子维度的总大小(递归终止:单个维度返回1) template <std::size_t... Dims> constexpr std::size_t sub_size() noexcept { return (Dims * ... * std::size_t{1}); } // 递归获取嵌套初始化列表中的元素:终止条件(最后一维) template <std::size_t Idx, class T> constexpr const T& get_elem(const std::initializer_list<T>& list) { return *std::next(list.begin(), Idx); } // 递归获取嵌套初始化列表中的元素:多层嵌套处理 template <std::size_t Idx, class T, std::size_t FirstDim, std::size_t... RestDims> constexpr const auto& get_elem(const std::tuple<const std::initializer_list<auto>&...>& lists_tuple) { constexpr std::size_t sub_dim_size = sub_size<RestDims...>(); // 计算当前元素属于第几个外层初始化列表 constexpr std::size_t list_index = Idx / sub_dim_size; // 计算在子列表中的索引 constexpr std::size_t inner_index = Idx % sub_dim_size; // 递归进入对应子列表 return get_elem<inner_index, T, RestDims...>(std::get<list_index>(lists_tuple)); } } template <class T, std::size_t... Dims> struct multi_array { static constexpr std::size_t size() noexcept { return (Dims * ... * std::size_t{1}); } // 1. 扁平化初始化:保留原有的聚合初始化能力,直接传递参数给底层数组 template <class... Args> requires (sizeof...(Args) == size() && (std::is_constructible_v<T, Args&&> && ...)) constexpr multi_array(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args&&> && ...) : _elems{std::forward<Args>(args)...} {} // 2. 嵌套初始化:入口构造函数,打包初始化列表并转发给索引展开构造函数 template <class... InitLists> requires (sizeof...(InitLists) == Dims[0]) constexpr multi_array(const std::initializer_list<InitLists>&... lists) noexcept : multi_array(std::make_index_sequence<size(){}, std::tie(lists...)) {} // 3. 内部初始化构造函数:用index_sequence展开每个元素的初始化 template <std::size_t... Idxs> constexpr multi_array(std::index_sequence<Idxs...>, const auto& lists_tuple) noexcept : _elems{ detail::get_elem<Idxs, T, Dims...>(lists_tuple)... } {} std::array<T, size()> _elems; // 禁止复制/移动,与元素类型保持一致 multi_array(const multi_array&) = delete; multi_array(multi_array&&) = delete; multi_array& operator=(const multi_array&) = delete; multi_array& operator=(multi_array&&) = delete; }; // 测试代码 int main() { // 原有的扁平化初始化依然有效 multi_array<non_movable, 3, 2> arr1 { non_movable(0), non_movable(1), non_movable(2), non_movable(3), non_movable(4), non_movable(5) }; // 嵌套初始化现在可以正常工作 multi_array<non_movable, 3, 2> arr2 { { non_movable(0), non_movable(1) }, { non_movable(2), non_movable(3) }, { non_movable(4), non_movable(5) } }; (void)arr1; (void)arr2; }
实现原理说明
- 递归元素定位:
detail::get_elem函数根据目标索引,递归计算出该元素在嵌套初始化列表中的位置——先确定属于哪个外层列表,再进入内层列表继续查找,直到最后一维直接返回元素。 - Index Sequence展开:利用
std::index_sequence生成从0到数组总长度-1的索引序列,每个索引对应底层数组的一个位置,通过get_elem获取对应的初始化元素后,直接传递给std::array的构造函数。 - 无移动/复制保证:所有元素都是直接从初始化列表中的prvalue构造到底层数组的内存位置,没有临时对象的创建和移动操作,完美兼容不可移动、不可复制类型。
这样既满足了嵌套初始化的需求,又保留了一维数组的底层实现,同时完全符合C++17及以上的复制消除规则,不需要依赖元素的移动构造函数。
内容的提问来源于stack exchange,提问作者L. F.
相关产品推荐
相关产品推荐

