I took the liberty of revisiting the source code of your dr_tensor container and implementing a draft of the tensor container, making some design changes to make it more flexible and lightweight.

Among other things, I believe that the implementation of the tensor container should take inspiration from the specifications of some STL containers, such as std::vector, and from the specification of std::mdspan because they represent the real updates that the C++ language has had in recent years. While the role of the tensor container should be to extend the functionality of std::valarray by providing an interface for operating on multidimensional arrays, the existence of the tensor container should represent an evolution of std::valarray, an evolution that it was never carried forward because the class stopped being actively supported from the beginning. In this sense, if we succeed in carrying out the proposal and it is approved by the committee, I hope that the tensor container will be more successful than std::valarray and can be updated over time.

The class definition may be the following:

template <class T, class Extents, class LayoutPolicy = std::layout_right, class ContainerPolicy = /* see-below */, class Alloc = std::allocator<T>>

class tensor;

The type T must meet the requirements of NumericType.

The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type meets the requirements of Erasable, but many member functions impose stricter requirements. This container (but not its members) can be instantiated with an incomplete element type if the allocator satisfies the allocator completeness requirements.

using value_type = T;

using reference = value_type&;

using const_reference = const value_type&;

using pointer = std::allocator_traits<Alloc>::pointer;

using const_pointer = std::allocator_traits<Alloc>::const_pointer

using size_type = [ Unsigned integer type (usually std::size_t) ];

using difference_type = [ Signed integer type (usually std::ptrdiff_t) ];

using iterator = [ LegacyRandomAccessIterator, contiguous_iterator, and ConstexprIterator to value_type ];

using const_iterator = [ LegacyRandomAccessIterator, contiguous_iterator, and ConstexprIterator to const value_type ];

using reverse_iterator = reverse_iterator<iterator>;

using accessor_type = ContainerPolicy;

using allocator_type = Alloc;

constexpr tensor() noexcept(std::is_default_constructible_v<Alloc>);

[ Note: you cannot indicate the default constructor as noexcept (only conditionally noexcept), because the standard does not guarantee that the allocator's default constructor will not throw exceptions ]

constexpr explicit tensor(const allocator_type& a) noexcept;

constexpr explicit tensor(size_type n, const allocator_type& a = allocator_type());

constexpr tensor(size_type n, const value_type& v, const allocator_type& a = allocator_type());

constexpr tensor(const tensor& x);

constexpr tensor(const tensor& x, const allocator_type& a);

constexpr tensor(tensor&& x) noexcept;

constexpr tensor(tensor&& x, const allocator_type& a) noexcept;

[Note: it doesn't make sense to make noexcept conditional, because the standard guarantees that the allocator's copy and move constructor doesn't throw exceptions, and the container's move constructor doesn't have to do any memory allocating operations.]

constexpr tensor(std::initializer_list<value_type> il, const allocator_type& a = allocator_type());

template <class InputIter> constexpr tensor(InputIter first, InputIter last, const allocator_type& a = allocator_type());

[ Note: this overload participates in overload resolution only if InputIt satisfies LegacyInputIterator ]

template <class R> constexpr explicit tensor(std::from_range_t, R&& rg, const allocator_type& a = allocator_type());

[ Note: this overload participates in overload resolution only if R satisfies at least the std::ranges::input_range concept ]

constexpr ~tensor();

constexpr tensor& operator=(std::initializer_list<value_type> il);

constexpr tensor& operator=(const tensor& x);

constexpr tensor& operator=(tensor&& x) noexcept(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value

|| std::allocator_traits<Allocator>::is_always_equal::value);

constexpr allocator_type get_allocator() const noexcept;

[ Note: set_allocator() was removed because changing the allocator doesn't make much sense, also because, if std::allocator_traits<Alloc>::is_always_equal::value is not true, it might be necessary to deallocate already existing memory with the previous allocator and re-allocate memory with the new allocator ]

[[nodiscard]] constexpr bool empty() const noexcept;

constexpr size_type size() const noexcept;

constexpr size_type capacity() const noexcept;

constexpr size_type max_size() const noexcept;

constexpr reference operator[](size_type ... indices);

constexpr const_reference operator[](size_type... indeces) const;

constexpr reference at(size_type ... indices);

constexpr const_reference at(size_type... indeces) const;

[ Note: if pos is not within the range of the container, an exception of type std::out_of_range is thrown ]

constexpr swap(tensor& x) noexcept(std::allocator_traits<Allocator>::propagate_on_container_swap::value || std::allocator_traits<Allocator>::is_always_equal::value);

void resize(size_type n);

Among other things, I believe that the implementation of the tensor container should take inspiration from the specifications of some STL containers, such as std::vector, and from the specification of std::mdspan because they represent the real updates that the C++ language has had in recent years. While the role of the tensor container should be to extend the functionality of std::valarray by providing an interface for operating on multidimensional arrays, the existence of the tensor container should represent an evolution of std::valarray, an evolution that it was never carried forward because the class stopped being actively supported from the beginning. In this sense, if we succeed in carrying out the proposal and it is approved by the committee, I hope that the tensor container will be more successful than std::valarray and can be updated over time.

The class definition may be the following:

template <class T, class Extents, class LayoutPolicy = std::layout_right, class ContainerPolicy = /* see-below */, class Alloc = std::allocator<T>>

class tensor;

The type T must meet the requirements of NumericType.

The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type meets the requirements of Erasable, but many member functions impose stricter requirements. This container (but not its members) can be instantiated with an incomplete element type if the allocator satisfies the allocator completeness requirements.

The type Extents specifies number of dimensions, their sizes, and which are known at compile time. Must be a specialization of std::extents.

The type LayoutPolicy specifies how to convert multidimensional index to underlying 1D index
(column-major 3D array, symmetric triangular 2D matrix, etc)
.The type ContainerPolicy specifies how the elements in the container must be accessed.

The type Alloc must meet the requirements of Allocator. The program is ill-formed if Allocator::value_type is not the same as T.using value_type = T;

using reference = value_type&;

using const_reference = const value_type&;

using pointer = std::allocator_traits<Alloc>::pointer;

using const_pointer = std::allocator_traits<Alloc>::const_pointer

using size_type = [ Unsigned integer type (usually std::size_t) ];

using difference_type = [ Signed integer type (usually std::ptrdiff_t) ];

using iterator = [ LegacyRandomAccessIterator, contiguous_iterator, and ConstexprIterator to value_type ];

using const_iterator = [ LegacyRandomAccessIterator, contiguous_iterator, and ConstexprIterator to const value_type ];

using reverse_iterator = reverse_iterator<iterator>;

using const_reverse_iterator = reverse_iterator<const_iterator>;

using extents_type = Extents;

using layout_type = LayoutPolicy;using accessor_type = ContainerPolicy;

using allocator_type = Alloc;

constexpr tensor() noexcept(std::is_default_constructible_v<Alloc>);

[ Note: you cannot indicate the default constructor as noexcept (only conditionally noexcept), because the standard does not guarantee that the allocator's default constructor will not throw exceptions ]

constexpr explicit tensor(const allocator_type& a) noexcept;

constexpr explicit tensor(size_type n, const allocator_type& a = allocator_type());

constexpr tensor(size_type n, const value_type& v, const allocator_type& a = allocator_type());

constexpr tensor(const tensor& x);

constexpr tensor(const tensor& x, const allocator_type& a);

constexpr tensor(tensor&& x) noexcept;

constexpr tensor(tensor&& x, const allocator_type& a) noexcept;

[Note: it doesn't make sense to make noexcept conditional, because the standard guarantees that the allocator's copy and move constructor doesn't throw exceptions, and the container's move constructor doesn't have to do any memory allocating operations.]

constexpr tensor(std::initializer_list<value_type> il, const allocator_type& a = allocator_type());

template <class InputIter> constexpr tensor(InputIter first, InputIter last, const allocator_type& a = allocator_type());

[ Note: this overload participates in overload resolution only if InputIt satisfies LegacyInputIterator ]

template <class R> constexpr explicit tensor(std::from_range_t, R&& rg, const allocator_type& a = allocator_type());

[ Note: this overload participates in overload resolution only if R satisfies at least the std::ranges::input_range concept ]

constexpr ~tensor();

constexpr tensor& operator=(std::initializer_list<value_type> il);

constexpr tensor& operator=(const tensor& x);

constexpr tensor& operator=(tensor&& x) noexcept(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value

|| std::allocator_traits<Allocator>::is_always_equal::value);

constexpr allocator_type get_allocator() const noexcept;

[ Note: set_allocator() was removed because changing the allocator doesn't make much sense, also because, if std::allocator_traits<Alloc>::is_always_equal::value is not true, it might be necessary to deallocate already existing memory with the previous allocator and re-allocate memory with the new allocator ]

[[nodiscard]] constexpr bool empty() const noexcept;

constexpr size_type size() const noexcept;

constexpr size_type capacity() const noexcept;

constexpr size_type max_size() const noexcept;

constexpr reference operator[](size_type ... indices);

constexpr const_reference operator[](size_type... indeces) const;

constexpr reference at(size_type ... indices);

constexpr const_reference at(size_type... indeces) const;

[ Note: if pos is not within the range of the container, an exception of type std::out_of_range is thrown ]

constexpr swap(tensor& x) noexcept(std::allocator_traits<Allocator>::propagate_on_container_swap::value || std::allocator_traits<Allocator>::is_always_equal::value);

void resize(size_type n);

void resize(size_type n, const value_type& v);

template <class T, class Extents, class LayoutPolicy, class ContainerPolicy, class Alloc>

constexpr bool operator==(const tensor<T, Extents, LayoutPolicy, ContainerPolicy, Alloc>& x, const tensor<T, Extents, LayoutPolicy, ContainerPolicy, Alloc>& y);

template <class T, class Extents, class LayoutPolicy, class ContainerPolicy, class Alloc>

constexpr auto operator<=>(const tensor<T, Extents, LayoutPolicy, ContainerPolicy, Alloc>& x, const tensor<T, Extents, LayoutPolicy, ContainerPolicy, Alloc>& y);

For the moment, we must work on custom constructors for std::mdspan of others. The most important thing is creating few overloaded constructors, that do not cause ambiguity with the already-existing overloaded constructors. I omitted all the member overloaded operators to perform Linear Algebra operations, since they can be easily found in the implementation of std::valarray. Other member functions to perform more specific operations might be introduced in the future. All non-member overloaded operators to perform Linear Algebra operations use the member overloaded operators under the hood.

Furthermore, we should think more about how to integrate the std::mdspan class, taking from it only the interface to manage multidimensional arrays, and some STL containers, taking from them the allocator-aware interface.