Background
Suppose that I am trying to implement a fixed-size multi-dimensional array using a flat array:
template <class T, std::size_t... Dims>
struct multi_array {
static constexpr std::size_t size() noexcept
{
return (Dims * ... * std::size_t{1});
}
std::array<T, size()> _elems;
};
The _elems
member is made public to enable aggregate initialization for non-copyable, non-movable types: (suppose that non_movable
has an explicit (int)
constructor)
multi_array<non_movable, 2, 3> arr {
non_movable(0), non_movable(1), non_movable(2),
non_movable(3), non_movable(4), non_movable(5)
};
This compiles thanks to C++17 guaranteed copy elision — the corresponding elements of _elems
are directly initialized from the unmaterialized prvalues, without requiring move constructors.
Problem
Now the problem is: in the above declaration, the multi-dimensional array is initialized like a one-dimensional array. I'll refer to this as "flat initialization", in contrast to "nested initialization":
multi_array<non_movable, 2, 3> arr {
{ non_movable(0), non_movable(1), non_movable(2) },
{ non_movable(3), non_movable(4), non_movable(5) }
}; // error: too many initializers for 'multi_array<non_movable, 3, 2>'
How can we enable nested initialization without having to change the underlying container used to implement multi_array
from a one-dimensional array to a multi-dimensional array?
I guess that this would require a custom constructor, but I have no idea how to pass unmaterialized prvalues "transparently" through constructors. All I can think of is constructing a parameter out of them and then moving from the parameter, which doesn't work for non-movable types.
Minimal reproducible example
#include <array>
#include <cstddef>
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;
};
template <class T, std::size_t... Dims>
struct multi_array {
static constexpr std::size_t size() noexcept
{
return (Dims * ... * std::size_t{1});
}
std::array<T, size()> _elems;
};
int main()
{
multi_array<non_movable, 3, 2> arr {
non_movable(0), non_movable(1), non_movable(2),
non_movable(3), non_movable(4), non_movable(5)
};
// multi_array<non_movable, 3, 2> arr {
// { non_movable(0), non_movable(1), non_movable(2) },
// { non_movable(3), non_movable(4), non_movable(5) }
// };
(void)arr;
}