Few initial comments. I will do a full-review with checks against wording later today or tomorrow.
On Fri, Jun 27, 2025 at 11:37 AM Luc Grosheintz <luc.groshei...@gmail.com> wrote: > Implements the class mdspan as described in N4950, i.e. without P3029. > It also adds tests for mdspan. > > libstdc++-v3/ChangeLog: > > * include/std/mdspan (mdspan): New class. > * src/c++23/std.cc.in: Add std::mdspan. > * testsuite/23_containers/mdspan/class_mandate_neg.cc: New test. > * testsuite/23_containers/mdspan/mdspan.cc: New test. > * testsuite/23_containers/mdspan/layout_like.h: Add class > LayoutLike which models a user-defined layout. > > Signed-off-by: Luc Grosheintz <luc.groshei...@gmail.com> > --- > libstdc++-v3/include/std/mdspan | 282 +++++++++ > libstdc++-v3/src/c++23/std.cc.in | 3 +- > .../23_containers/mdspan/class_mandate_neg.cc | 58 ++ > .../23_containers/mdspan/layout_like.h | 63 ++ > .../testsuite/23_containers/mdspan/mdspan.cc | 540 ++++++++++++++++++ > 5 files changed, 945 insertions(+), 1 deletion(-) > create mode 100644 > libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc > create mode 100644 > libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h > create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc > > diff --git a/libstdc++-v3/include/std/mdspan > b/libstdc++-v3/include/std/mdspan > index e198d65bba3..852f881971e 100644 > --- a/libstdc++-v3/include/std/mdspan > +++ b/libstdc++-v3/include/std/mdspan > @@ -1052,6 +1052,288 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > { return __p + __i; } > }; > > + namespace __mdspan > + { > + template<typename _Extents, typename _IndexType, size_t _Nm> > + constexpr bool > + __is_multi_index(const _Extents& __exts, span<_IndexType, _Nm> > __indices) > + { > + static_assert(__exts.rank() == _Nm); > + for (size_t __i = 0; __i < __exts.rank(); ++__i) > + if (__indices[__i] >= __exts.extent(__i)) > + return false; > + return true; > + } > + } > + > + template<typename _ElementType, typename _Extents, > + typename _LayoutPolicy = layout_right, > + typename _AccessorPolicy = default_accessor<_ElementType>> > + class mdspan > + { > + static_assert(!is_array_v<_ElementType>, > + "ElementType must not be an array type"); > + static_assert(!is_abstract_v<_ElementType>, > + "ElementType must not be an abstract class type"); > + static_assert(__mdspan::__is_extents<_Extents>, > + "Extents must be a specialization of std::extents"); > + static_assert(is_same_v<_ElementType, > + typename _AccessorPolicy::element_type>); > + > + public: > + using extents_type = _Extents; > + using layout_type = _LayoutPolicy; > + using accessor_type = _AccessorPolicy; > + using mapping_type = typename layout_type::template > mapping<extents_type>; > + using element_type = _ElementType; > + using value_type = remove_cv_t<element_type>; > + using index_type = typename extents_type::index_type; > + using size_type = typename extents_type::size_type; > + using rank_type = typename extents_type::rank_type; > + using data_handle_type = typename accessor_type::data_handle_type; > + using reference = typename accessor_type::reference; > + > + static constexpr rank_type > + rank() noexcept { return extents_type::rank(); } > + > + static constexpr rank_type > + rank_dynamic() noexcept { return extents_type::rank_dynamic(); } > + > + static constexpr size_t > + static_extent(rank_type __r) noexcept > + { return extents_type::static_extent(__r); } > + > + constexpr index_type > + extent(rank_type __r) const noexcept { return > extents().extent(__r); } > + > + constexpr > + mdspan() > + requires (rank_dynamic() > 0 && > + is_default_constructible_v<data_handle_type> && > + is_default_constructible_v<mapping_type> && > + is_default_constructible_v<accessor_type>) > + : _M_accessor{}, _M_mapping{}, _M_handle{} > + { } > + > + constexpr > + mdspan(const mdspan& __other) = default; > + > + constexpr > + mdspan(mdspan&& __other) = default; > + > + template<__mdspan::__valid_index_type<index_type>... _OIndexTypes> > + requires ((sizeof...(_OIndexTypes) == rank() > + || sizeof...(_OIndexTypes) == rank_dynamic()) > + && is_constructible_v<mapping_type, extents_type> > + && is_default_constructible_v<accessor_type>) > + constexpr explicit > + mdspan(data_handle_type __handle, _OIndexTypes... __exts) > + : _M_accessor{}, > + > _M_mapping(_Extents(static_cast<index_type>(std::move(__exts))...)), > + _M_handle(std::move(__handle)) > + { } > + > + template<__mdspan::__valid_index_type<index_type> _OIndexType, > + size_t _Nm> > + requires ((_Nm == rank() || _Nm == rank_dynamic()) > + && is_constructible_v<mapping_type, extents_type> > + && is_default_constructible_v<accessor_type>) > + constexpr explicit(_Nm != rank_dynamic()) > + mdspan(data_handle_type __handle, span<_OIndexType, _Nm> __exts) > + : _M_accessor{}, _M_mapping(extents_type(__exts)), > + _M_handle(std::move(__handle)) > + { } > + > + template<__mdspan::__valid_index_type<index_type> _OIndexType, > + size_t _Nm> > + requires ((_Nm == rank() || _Nm == rank_dynamic()) > + && is_constructible_v<mapping_type, extents_type> > + && is_default_constructible_v<accessor_type>) > + constexpr explicit(_Nm != rank_dynamic()) > + mdspan(data_handle_type __handle, const array<_OIndexType, _Nm>& > __exts) > + : _M_accessor{}, _M_mapping(extents_type(__exts)), > + _M_handle(std::move(__handle)) > + { } > + > + constexpr > + mdspan(data_handle_type __handle, const extents_type& __exts) > + requires (is_constructible_v<mapping_type, const extents_type&> > + && is_default_constructible_v<accessor_type>) > + : _M_accessor{}, _M_mapping(__exts), _M_handle(std::move(__handle)) > + { } > + > + constexpr > + mdspan(data_handle_type __handle, const mapping_type& __mapping) > + requires (is_default_constructible_v<accessor_type>) > + : _M_accessor{}, _M_mapping(__mapping), > _M_handle(std::move(__handle)) > + { } > + > + constexpr > + mdspan(data_handle_type __handle, const mapping_type& __mapping, > + const accessor_type& __accessor) > + : _M_accessor(__accessor), _M_mapping(__mapping), > + _M_handle(std::move(__handle)) > + { } > + > + template<typename _OElementType, typename _OExtents, typename > _OLayout, > + typename _OAccessor> > + requires (is_constructible_v<mapping_type, > + const typename _OLayout::mapping<_OExtents>&> > + && is_constructible_v<accessor_type, const _OAccessor&>) > + constexpr explicit(!(is_convertible_v< > + const typename _OLayout::mapping<_OExtents>&, mapping_type> > + && is_convertible_v<const _OAccessor&, accessor_type>)) > + mdspan(const mdspan<_OElementType, _OExtents, _OLayout, > _OAccessor>& > + __other) > + : _M_accessor(__other.accessor()), _M_mapping(__other.mapping()), > + _M_handle(__other.data_handle()) > + { > + static_assert(is_constructible_v<data_handle_type, > + const typename _OAccessor::data_handle_type&>); > + static_assert(is_constructible_v<extents_type, _OExtents>); > + } > + > + constexpr mdspan& > + operator=(const mdspan& __other) = default; > + > + constexpr mdspan& > + operator=(mdspan&& __other) = default; > + > + template<__mdspan::__valid_index_type<index_type>... _OIndexTypes> > + requires (sizeof...(_OIndexTypes) == rank()) > + constexpr reference > + operator[](_OIndexTypes... __indices) const > + { > + auto __checked_call = [this](auto... __idxs) -> index_type > + { > + if constexpr > (!__mdspan::__standardized_mapping<mapping_type>) > As mentioned in e-mail for PATCH 1/5, I would always place a check here. > + __glibcxx_assert(__mdspan::__is_multi_index(extents(), > + span<const index_type, sizeof...(__idxs)>({__idxs...}))); > + return _M_mapping(__idxs...); > + }; > + > + auto __index = __checked_call( > + static_cast<index_type>(std::move(__indices))...); > + return _M_accessor.access(_M_handle, __index); > + } > + > + template<__mdspan::__valid_index_type<index_type> _OIndexType> > + constexpr reference > + operator[](span<_OIndexType, rank()> __indices) const > + { > + auto __call = [&]<size_t... _Counts>(index_sequence<_Counts...>) > + -> reference > + { return (*this)[index_type(as_const(__indices[_Counts]))...]; > }; > + return __call(make_index_sequence<rank()>()); > + } > + > + template<__mdspan::__valid_index_type<index_type> _OIndexType> > + constexpr reference > + operator[](const array<_OIndexType, rank()>& __indices) const > + { return (*this)[span<const _OIndexType, rank()>(__indices)]; } > + > + constexpr size_type > + size() const noexcept > + { > + __glibcxx_assert(cmp_less_equal(_M_mapping.required_span_size(), > + numeric_limits<size_t>::max())); > + return size_type(__mdspan::__fwd_prod(extents(), rank())); > + } > + > + [[nodiscard]] constexpr bool > + empty() const noexcept > + { > + return __mdspan::__empty(extents()); > + } > + > + friend constexpr void > + swap(mdspan& __x, mdspan& __y) noexcept > + { > + std::swap(__x._M_mapping, __y._M_mapping); > + std::swap(__x._M_accessor, __y._M_accessor); > + std::swap(__x._M_handle, __y._M_handle); > + } > + > + constexpr const extents_type& > + extents() const noexcept { return _M_mapping.extents(); } > + > + constexpr const data_handle_type& > + data_handle() const noexcept { return _M_handle; } > + > + constexpr const mapping_type& > + mapping() const noexcept { return _M_mapping; } > + > + constexpr const accessor_type& > + accessor() const noexcept { return _M_accessor; } > + > + static constexpr bool > + is_always_unique() { return mapping_type::is_always_unique(); } > + > + static constexpr bool > + is_always_exhaustive() { return > mapping_type::is_always_exhaustive(); } > + > + static constexpr bool > + is_always_strided() { return mapping_type::is_always_strided(); } > + > + constexpr bool > + is_unique() const { return _M_mapping.is_unique(); } > + > + constexpr bool > + is_exhaustive() const { return _M_mapping.is_exhaustive(); } > + > + constexpr bool > + is_strided() const { return _M_mapping. is_strided(); } > + > + constexpr index_type > + stride(rank_type __r) const { return _M_mapping.stride(__r); } > + > + private: > + [[no_unique_address]] accessor_type _M_accessor; > + [[no_unique_address]] mapping_type _M_mapping; > + [[no_unique_address]] data_handle_type _M_handle; > + }; > + > + template<typename _CArray> > + requires(is_array_v<_CArray> && rank_v<_CArray> == 1) > + mdspan(_CArray&) > + -> mdspan<remove_all_extents_t<_CArray>, > + extents<size_t, extent_v<_CArray, 0>>>; > + > + template<typename _Pointer> > + requires(is_pointer_v<remove_reference_t<_Pointer>>) > + mdspan(_Pointer&&) > + -> mdspan<remove_pointer_t<remove_reference_t<_Pointer>>, > extents<size_t>>; > + > + template<typename _ElementType, typename... _Integrals> > + requires((is_convertible_v<_Integrals, size_t> && ...) > + && sizeof...(_Integrals) > 0) > + explicit mdspan(_ElementType*, _Integrals...) > + -> mdspan<_ElementType, dextents<size_t, sizeof...(_Integrals)>>; > We could use __mdspan::__dynamic_extent<Integrals>... here instead of computing dextents. This would make implementation of P3029R1 simpler. > + > + template<typename _ElementType, typename _OIndexType, size_t _Nm> > + mdspan(_ElementType*, span<_OIndexType, _Nm>) > + -> mdspan<_ElementType, dextents<size_t, _Nm>>; > + > + template<typename _ElementType, typename _OIndexType, size_t _Nm> > + mdspan(_ElementType*, const array<_OIndexType, _Nm>&) > + -> mdspan<_ElementType, dextents<size_t, _Nm>>; > + > + template<typename _ElementType, typename _IndexType, size_t... > _ExtentsPack> > + mdspan(_ElementType*, const extents<_IndexType, _ExtentsPack...>&) > + -> mdspan<_ElementType, extents<_IndexType, _ExtentsPack...>>; > + > + template<typename _ElementType, typename _MappingType> > + mdspan(_ElementType*, const _MappingType&) > + -> mdspan<_ElementType, typename _MappingType::extents_type, > + typename _MappingType::layout_type>; > + > + template<typename _MappingType, typename _AccessorType> > + mdspan(const typename _AccessorType::data_handle_type&, const > _MappingType&, > + const _AccessorType&) > + -> mdspan<typename _AccessorType::element_type, > + typename _MappingType::extents_type, > + typename _MappingType::layout_type, _AccessorType>; > + > _GLIBCXX_END_NAMESPACE_VERSION > } > #endif > diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/ > std.cc.in > index e692caaa5f9..b62114be467 100644 > --- a/libstdc++-v3/src/c++23/std.cc.in > +++ b/libstdc++-v3/src/c++23/std.cc.in > @@ -1851,7 +1851,8 @@ export namespace std > using std::layout_right; > using std::layout_stride; > using std::default_accessor; > - // FIXME layout_left_padded, layout_right_padded, aligned_accessor and > mdspan > + using std::mdspan; > + // FIXME layout_left_padded, layout_right_padded, aligned_accessor, > mdsubspan > } > #endif > > diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc > new file mode 100644 > index 00000000000..96b9a2c41bb > --- /dev/null > +++ b/libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc > @@ -0,0 +1,58 @@ > +// { dg-do compile { target c++23 } } > +#include<mdspan> > + > +#include <cstdint> > +#include "layout_like.h" > + > +struct ExtentsLike > +{ > + using index_type = int; > + using size_type = unsigned int; > + using rank_type = size_t; > + > + static constexpr size_t rank() { return 1; } > + static constexpr size_t rank_dynamic() { return 0; } > +}; > + > +constexpr bool > +test_custom_extents_type() > +{ > + std::mdspan<double, ExtentsLike> md1; // { dg-error "required from > here" } > + return true; > +} > +static_assert(test_custom_extents_type()); > + > +constexpr bool > +test_element_type_mismatch() > +{ > + using E = std::extents<int, 1>; > + using L = std::layout_right; > + using A = std::default_accessor<double>; > + > + [[maybe_unused]] std::mdspan<float, E, L, A> md2; // { dg-error > "required from here" } > + return true; > +}; > +static_assert(test_element_type_mismatch()); > + > +template<typename Layout> > +constexpr bool > +test_invalid_multi_index() > I think I would put this test in separate file, like out_of_bound_neg.cc. > +{ > + > + double data = 1.1; > + auto m = typename Layout::mapping<std::extents<int, 1, 2, 3>>{}; > + auto md = std::mdspan(&data, m); > + > + [[maybe_unused]] double x = md[0, 2, 2]; // { dg-error "expansion of" } > + return true; > +}; > +static_assert(test_invalid_multi_index<LayoutLike>()); // { dg-error > "expansion of" } > +static_assert(test_invalid_multi_index<std::layout_left>()); // { > dg-error "expansion of" } > +static_assert(test_invalid_multi_index<std::layout_right>()); // { > dg-error "expansion of" } > +static_assert(test_invalid_multi_index<std::layout_stride>()); // { > dg-error "expansion of" } > + > +// { dg-prune-output "Extents must be a specialization of std::extents" } > +// { dg-prune-output "no type named '_S_storage'" } > +// { dg-prune-output "non-constant condition" } > +// { dg-prune-output "static assertion failed" } > +// { dg-prune-output "__glibcxx_assert" } > diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h > b/libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h > new file mode 100644 > index 00000000000..17ccf52c6a6 > --- /dev/null > +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h > @@ -0,0 +1,63 @@ > +#pragma once > + > +struct LayoutLike > +{ > + template<typename Extents> > + class mapping > + { > + public: > + using extents_type = Extents; > + using index_type = typename extents_type::index_type; > + using size_type = typename extents_type::size_type; > + using rank_type = typename extents_type::rank_type; > + using layout_type = LayoutLike; > + > + constexpr > + mapping() noexcept = default; > + > + constexpr > + mapping(Extents exts) > + : m_exts(exts) > + { } > + > + constexpr const extents_type& > + extents() const noexcept { return m_exts; } > + > + constexpr index_type > + required_span_size() const noexcept > + { return 1; } > + > + template<typename... Indices> > + requires (sizeof...(Indices) == extents_type::rank()) > + constexpr index_type > + operator()(Indices...) const noexcept > + { return 0; } > + > + static constexpr bool > + is_always_unique() noexcept > + { return false; } > + > + static constexpr bool > + is_always_exhaustive() noexcept > + { return false; } > + > + static constexpr bool > + is_always_strided() noexcept > + { return false; } > + > + static constexpr bool > + is_unique() noexcept > + { return false; } > + > + static constexpr bool > + is_exhaustive() noexcept > + { return false; } > + > + static constexpr bool > + is_strided() noexcept > + { return false; } > + > + private: > + Extents m_exts; > + }; > +}; > diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc > new file mode 100644 > index 00000000000..d2672878d69 > --- /dev/null > +++ b/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc > @@ -0,0 +1,540 @@ > +// { dg-do run { target c++23 } } > +#include <mdspan> > + > +#include <testsuite_hooks.h> > +#include "extents/int_like.h" > +#include "layout_like.h" > + > +constexpr auto dyn = std::dynamic_extent; > + > +template<typename MDSpan, typename T, typename E, typename L = > std::layout_right, > + typename A = std::default_accessor<T>> > + constexpr void > + assert_typedefs() > + { > + static_assert(std::same_as<typename MDSpan::extents_type, E>); > + static_assert(std::same_as<typename MDSpan::layout_type, L>); > + static_assert(std::same_as<typename MDSpan::accessor_type, A>); > + static_assert(std::same_as<typename MDSpan::mapping_type, > + typename L::mapping<E>>); > + static_assert(std::same_as<typename MDSpan::element_type, T>); > + static_assert(std::same_as<typename MDSpan::value_type, > + std::remove_const_t<T>>); > + static_assert(std::same_as<typename MDSpan::index_type, > + typename E::index_type>); > + static_assert(std::same_as<typename MDSpan::size_type, > + typename E::size_type>); > + static_assert(std::same_as<typename MDSpan::rank_type, > + typename E::rank_type>); > + static_assert(std::same_as<typename MDSpan::data_handle_type, > + typename A::data_handle_type>); > + static_assert(std::same_as<typename MDSpan::reference, > + typename A::reference>); > + } > + > +template<typename T, typename E, typename L, template<typename U> > typename A> > + constexpr void > + test_typedefs() > + { assert_typedefs<std::mdspan<T, E, L, A<T>>, T, E, L, A<T>>(); } > + > +constexpr void > +test_typedefs_all() > +{ > + using E = std::extents<int, 1, 2>; > + using L = std::layout_left; > + > + test_typedefs<double, E, L, std::default_accessor>(); > + test_typedefs<const double, E, L, std::default_accessor>(); > +} > + > +template<typename MDSpan> > + constexpr void > + test_rank() > + { > + using Extents = typename MDSpan::extents_type; > + static_assert(MDSpan::rank() == Extents::rank()); > + static_assert(MDSpan::rank_dynamic() == Extents::rank_dynamic()); > + } > + > +constexpr bool > +test_rank_all() > +{ > + test_rank<std::mdspan<double, std::extents<int>>>(); > + test_rank<std::mdspan<double, std::extents<int, 1>>>(); > + test_rank<std::mdspan<double, std::extents<int, dyn>>>(); > + return true; > +} > + > +template<typename Extents> > + constexpr void > + test_extent(Extents exts) > + { > + double data = 1.0; > + auto md = std::mdspan(&data, exts); > + using MDSpan = decltype(md); > + > + for(size_t i = 0; i < MDSpan::rank(); ++i) > + { > + VERIFY(MDSpan::static_extent(i) == Extents::static_extent(i)); > + VERIFY(md.extent(i) == exts.extent(i)); > + } > + } > + > +constexpr bool > +test_extent_all() > +{ > + // For rank == 0, check existence of the methods without calling them. > + test_extent(std::extents<int>{}); > + test_extent(std::extents<int, 0>{}); > + test_extent(std::extents<int, dyn>{}); > + return true; > +} > + > +template<typename MDSpan> > + constexpr void > + test_class_properties() > + { > + static_assert(std::copyable<MDSpan>); > + static_assert(std::is_nothrow_move_constructible_v<MDSpan>); > + static_assert(std::is_nothrow_move_assignable_v<MDSpan>); > + static_assert(std::is_nothrow_swappable_v<MDSpan>); > + constexpr bool trivially_copyable = > + std::is_trivially_copyable_v<typename MDSpan::accessor_type> > + && std::is_trivially_copyable_v<typename MDSpan::mapping_type> > + && std::is_trivially_copyable_v<typename MDSpan::data_handle_type>; > + static_assert(std::is_trivially_copyable_v<MDSpan> == > trivially_copyable); > + } > + > +constexpr bool > +test_class_properties_all() > +{ > + test_class_properties<std::mdspan<double, std::extents<int>>>(); > + test_class_properties<std::mdspan<double, std::extents<int, 1>>>(); > + test_class_properties<std::mdspan<double, std::extents<int, dyn>>>(); > + return true; > +} > + > +constexpr bool > +test_default_ctor() > +{ > + static_assert(!std::is_default_constructible_v<std::mdspan<double, > + std::extents<int>>>); > + static_assert(!std::is_default_constructible_v<std::mdspan<double, > + std::extents<int, 1>>>); > + static_assert(std::is_default_constructible_v<std::mdspan<double, > + std::extents<int, dyn>>>); > + > + std::mdspan<double, std::extents<int, dyn>> md; > + VERIFY(md.data_handle() == nullptr); > + VERIFY(md.extent(0) == 0); > + return true; > +} > + > +constexpr bool > +test_from_other() > +{ > + using Extents = std::extents<int, 3, 5, 7>; > + auto exts = Extents{}; > + > + auto mapping = std::layout_right::mapping(exts); > + constexpr size_t n = mapping.required_span_size(); > + std::array<double, n> storage{}; > + > + auto md1 = std::mdspan(storage.data(), exts); > + auto md2 = std::mdspan<double, std::dextents<int, 3>>(md1); > + > + VERIFY(md1.data_handle() == md2.data_handle()); > + VERIFY(md1.size() == md2.size()); > + > + static_assert(!std::is_convertible_v< > + std::mdspan<double, std::extents<unsigned int, 2>>, > + std::mdspan<double, std::extents<int, 2>>>); > + > + return true; > +} > + > +template<typename T, typename E, typename L = std::layout_right, > + typename A = std::default_accessor<T>> > + constexpr void > + assert_deduced_typedefs(auto md) > + { assert_typedefs<decltype(md), T, E, L, A>(); } > + > +constexpr bool > +test_from_carray() > +{ > + constexpr size_t n = 5; > + double data[n] = {1.1, 2.2, 3.3, 4.4, 5.5}; > + > + auto md = std::mdspan(data); > + assert_deduced_typedefs<double, std::extents<size_t, n>>(md); > + VERIFY(md.rank() == 1); > + VERIFY(md.rank_dynamic() == 0); > + VERIFY(md[2] == data[2]); > + return true; > +} > + > +constexpr bool > +test_from_pointer() > +{ > + double value = 12.3; > + auto md = std::mdspan(&value); > + assert_deduced_typedefs<double, std::extents<size_t>>(md); > + VERIFY(md.rank() == 0); > + VERIFY(md.rank_dynamic() == 0); > + VERIFY(md[] == value); > + return true; > +} > + > +constexpr bool > +test_from_pointer_and_shape() > +{ > + constexpr size_t n = 6; > + std::array<double, n> data{1.1, 2.2, 3.3, 4.4, 5.5, 6.6}; > + std::array<int, 2> shape{2, 3}; > + std::span<const int, 2> shape_view(shape); > + > + auto verify = [&data](auto md) > + { > + assert_deduced_typedefs<double, std::dextents<size_t, 2>>(md); > + VERIFY(md.rank() == 2); > + VERIFY(md.rank_dynamic() == 2); > + VERIFY((md[0, 0]) == data[0]); > + VERIFY((md[0, 1]) == data[1]); > + VERIFY((md[1, 0]) == data[3]); > + }; > + > + verify(std::mdspan(data.data(), shape[0], shape[1])); > + verify(std::mdspan(data.data(), shape)); > + verify(std::mdspan(data.data(), shape_view)); > + > + std::mdspan<double, std::dextents<size_t, 2>> md1 = {data.data(), > shape}; > + verify(md1); > + > + std::mdspan<double, std::dextents<size_t, 2>> md2 = {data.data(), > shape_view}; > + verify(md2); > + > + static_assert(std::is_constructible_v< > + std::mdspan<float, std::extents<int, 3, 5>>, float*>); > + static_assert(!std::is_constructible_v< > + std::mdspan<float, std::extents<int, 3, 5>>, float*, int>); > + static_assert(std::is_constructible_v< > + std::mdspan<float, std::extents<int, 3, 5>>, float*, int, int>); > + static_assert(std::is_constructible_v< > + std::mdspan<float, std::extents<int, 3, 5>>, float*, std::span<int, > 0>>); > + static_assert(std::is_constructible_v< > + std::mdspan<float, std::extents<int, 3, 5>>, float*, std::span<int, > 2>>); > + static_assert(!std::is_convertible_v< > + float*, std::mdspan<float, std::extents<int, 3, 5>>>); > + > + static_assert(std::is_constructible_v< > + std::mdspan<float, std::dextents<int, 2>>, float*, std::span<int, > 2>>); > + static_assert(!std::is_constructible_v< > + std::mdspan<float, std::dextents<int, 2>>, float*, std::span<int, > dyn>>); > + return true; > +} > + > +constexpr bool > +test_from_extents() > +{ > + constexpr size_t n = 3*5*7; > + std::array<double, n> storage{}; > + using Extents = std::extents<int, 3, 5, 7>; > + auto exts = Extents{}; > + auto md = std::mdspan(storage.data(), exts); > + > + assert_deduced_typedefs<double, Extents>(md); > + VERIFY(md.data_handle() == storage.data()); > + VERIFY(md.extents() == exts); > + return true; > +} > + > +constexpr bool > +test_from_mapping() > +{ > + constexpr size_t n = 3*5*7; > + std::array<double, n> storage{}; > + using Extents = std::extents<int, 3, 5, 7>; > + > + auto exts = Extents{}; > + auto m = std::layout_left::mapping(exts); > + auto md = std::mdspan(storage.data(), m); > + > + assert_deduced_typedefs<double, Extents, std::layout_left>(md); > + VERIFY(md.data_handle() == storage.data()); > + VERIFY(md.mapping() == m); > + return true; > +} > + > +constexpr bool > +test_from_accessor() > +{ > + constexpr size_t n = 3*5*7; > + std::array<double, n> storage{}; > + using Extents = std::extents<int, 3, 5, 7>; > + > + auto exts = Extents{}; > + auto m = std::layout_left::mapping(exts); > + auto a = std::default_accessor<double>{}; > + auto md = std::mdspan(storage.data(), m, a); > + > + assert_deduced_typedefs<double, Extents, std::layout_left>(md); > + VERIFY(md.data_handle() == storage.data()); > + VERIFY(md.mapping() == m); > + return true; > +} > + > +void > +test_from_int_like() > +{ > + constexpr size_t n = 3*5*7; > + std::array<double, n> storage{}; > + > + auto verify = [&](auto md) > + { > + VERIFY(md.data_handle() == storage.data()); > + VERIFY(md.extent(0) == 3); > + VERIFY(md.extent(1) == 5); > + VERIFY(md.extent(2) == 7); > + > + VERIFY((md[IntLike(0), 0, IntLike(0)]) == 0.0); > + auto zero = std::array{IntLike(0), IntLike(0), IntLike(0)}; > + auto zero_view = std::span<IntLike, 3>{zero}; > + VERIFY((md[zero]) == 0.0); > + VERIFY((md[zero_view]) == 0.0); > + }; > + > + auto shape = std::array{IntLike(3), IntLike(5), IntLike(7)}; > + auto shape_view = std::span<IntLike, 3>{shape}; > + verify(std::mdspan(storage.data(), IntLike(3), 5, IntLike(7))); > + verify(std::mdspan(storage.data(), shape)); > + verify(std::mdspan(storage.data(), shape_view)); > +} > + > +template<typename T> > + class OpaqueAccessor > + { > + struct Handle > + { > + T * ptr; > + }; > + > + public: > + using element_type = T; > + using reference = T&; > + using data_handle_type = Handle; > + using offset_policy = OpaqueAccessor; > + > + reference > + access(data_handle_type p, size_t i) const > + { > + ++access_count; > + return p.ptr[i]; > + } > + > + typename offset_policy::data_handle_type > + offset(data_handle_type p, size_t i) const > + { > + ++offset_count; > + return typename offset_policy::data_handle_type{(void*)(p.ptr + i)}; > + } > + > + mutable size_t access_count = 0; > + mutable size_t offset_count = 0; > + }; > + > +void > +test_from_opaque_accessor() > +{ > + constexpr size_t n = 3*5*7; > + std::array<double, n> storage{}; > + using Extents = std::extents<int, 3, 5, 7>; > + > + auto exts = Extents{}; > + auto m = std::layout_left::mapping(exts); > + auto a = OpaqueAccessor<double>{}; > + auto handle = OpaqueAccessor<double>::data_handle_type{storage.data()}; > + auto md = std::mdspan(handle, m, a); > + > + using MDSpan = decltype(md); > + static_assert(std::same_as<MDSpan::accessor_type, decltype(a)>); > + > + VERIFY((md[0, 0, 0]) == 0.0); > + VERIFY(md.accessor().access_count == 1); > + > + VERIFY((md[2, 4, 6]) == 0.0); > + VERIFY(md.accessor().access_count == 2); > +} > + > +template<typename T, typename Base> > + class BaseClassAccessor > + { > + public: > + using element_type = T; > + using reference = Base&; > + using data_handle_type = T*; > + using offset_policy = BaseClassAccessor; > + > + static_assert(std::common_reference_with<reference&&, element_type&>); > + > + reference > + access(data_handle_type p, size_t i) const > + { return p[i]; } > + > + typename offset_policy::data_handle_type > + offset(data_handle_type p, size_t i) const > + { return typename offset_policy::data_handle_type{p + i}; } > + }; > + > +struct Base > +{ > + double value = 1.0; > +}; > + > +struct Derived : Base > +{ > + double value = 2.0; > +}; > + > +void > +test_from_base_class_accessor() > +{ > + constexpr size_t n = 3*5*7; > + std::array<Derived, n> storage{}; > + using Extents = std::extents<int, 3, 5, 7>; > + > + auto exts = Extents{}; > + auto m = std::layout_left::mapping(exts); > + auto a = BaseClassAccessor<Derived, Base>{}; > + auto md = std::mdspan(storage.data(), m, a); > + > + using MDSpan = decltype(md); > + static_assert(std::same_as<MDSpan::accessor_type, decltype(a)>); > + static_assert(std::same_as<decltype(md[0, 0, 0]), Base&>); > + VERIFY((md[0, 0, 0].value) == 1.0); > + VERIFY((md[2, 4, 6].value) == 1.0); > +} > + > +constexpr bool > +test_from_mapping_like() > +{ > + double data = 1.1; > + auto m = LayoutLike::mapping<std::extents<int, 1, 2, 3>>{}; > + auto md = std::mdspan(&data, m); > + VERIFY((md[0, 0, 0]) == data); > + VERIFY((md[0, 1, 2]) == data); > + return true; > +} > + > +template<typename MDSpan> > + constexpr void > + test_empty(MDSpan md) > + { > + VERIFY(md.empty() == (md.size() == 0)); > + } > + > +constexpr bool > +test_empty_all() > +{ > + test_empty(std::mdspan<double, std::extents<int, dyn>>{}); > + return true; > +} > + > +constexpr bool > +test_access() > +{ > + using Extents = std::extents<int, 3, 5, 7>; > + auto exts = Extents{}; > + > + auto mapping = std::layout_left::mapping(exts); > + constexpr size_t n = mapping.required_span_size(); > + std::array<double, n> storage{}; > + > + auto md = std::mdspan(storage.data(), mapping); > + static_assert(std::__mdspan::__mapping_alike<decltype(md)>); > + > + for(int i = 0; i < exts.extent(0); ++i) > + for(int j = 0; j < exts.extent(1); ++j) > + for(int k = 0; k < exts.extent(2); ++k) > + { > + std::array<int, 3> ijk{i, j, k}; > + storage[mapping(i, j, k)] = 1.0; > + VERIFY((md[i, j, k]) == 1.0); > + VERIFY((md[ijk]) == 1.0); > + VERIFY((md[std::span(ijk)]) == 1.0); > + storage[mapping(i, j, k)] = 0.0; > + } > + return true; > +} > + > +constexpr bool > +test_swap() > +{ > + using Extents = std::dextents<int, 2>; > + auto e1 = Extents{3, 5}; > + auto e2 = Extents{7, 11}; > + > + std::array<double, 3*5> s1{}; > + std::array<double, 7*11> s2{}; > + > + auto md1 = std::mdspan(s1.data(), e1); > + auto md2 = std::mdspan(s2.data(), e2); > + > + std::swap(md1, md2); > + > + VERIFY(md1.data_handle() == s2.data()); > + VERIFY(md2.data_handle() == s1.data()); > + > + VERIFY(md1.size() == s2.size()); > + VERIFY(md2.size() == s1.size()); > + return true; > +} > + > +int > +main() > +{ > + test_typedefs_all(); > + > + test_rank_all(); > + test_extent_all(); > + static_assert(test_extent_all()); > + > + test_class_properties_all(); > + static_assert(test_class_properties_all()); > + > + test_empty_all(); > + static_assert(test_empty_all()); > + > + test_default_ctor(); > + static_assert(test_default_ctor()); > + > + test_from_other(); > + static_assert(test_from_other()); > + > + test_from_carray(); > + static_assert(test_from_carray()); > + > + test_from_pointer_and_shape(); > + static_assert(test_from_pointer_and_shape()); > + > + test_from_extents(); > + static_assert(test_from_extents()); > + > + test_from_mapping(); > + static_assert(test_from_mapping()); > + > + test_from_accessor(); > + static_assert(test_from_accessor()); > + > + test_from_int_like(); > + test_from_opaque_accessor(); > + test_from_base_class_accessor(); > + test_from_mapping_like(); > + static_assert(test_from_mapping_like()); > + > + test_access(); > + static_assert(test_access()); > + > + test_swap(); > + static_assert(test_swap()); > + return 0; > +} > -- > 2.49.0 > >