On Tue, Jul 1, 2025 at 3:44 PM Luc Grosheintz <luc.groshei...@gmail.com> wrote:
> > > On 7/1/25 12:44, Tomasz Kaminski wrote: > > 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. > > The following isn't clear to me about P3029R1 and FTMs. Is P3029 also > applied when compiling C++20 and C++23 code; or only when compiling > C++26? > > Same question as an example. Should it be: > > explicit mdspan(_ElementType*, _Integrals...) > -> mdspan<_ElementType, > extents<size_t, __maybe_static_extent<_Integrals>...> > >; > > or > > explicit mdspan(_ElementType*, _Integrals...) > -> mdspan<_ElementType, > extents<size_t, > #if __cplusplus <= 202303L > __dynamic_extent<_Integrals>... > #else > __maybe_static_extent<_Integrals>... > #endif > >>; > > Can an FTM ever return that it's implementing a future standard, > e.g. when compiling C++23, can __cpp_lib_mdspan return 202406L? > Yes, if we apply it as DR, and that would be a plan here. So we want __maybe_static_extent for both span and mdspan in all modes. > > > > >> + > >> + 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 > >> > >> > > > >