I used TEST_MDSPAN_LAYOUT_LIKE_H as header guard. On Mon, Jul 7, 2025 at 10:58 AM Tomasz Kaminski <tkami...@redhat.com> wrote:
> > > On Fri, Jul 4, 2025 at 10: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. This commit completes the implementation >> of P0009, i.e. the C++23 part <mdspan>. >> >> PR libstdc++/107761 >> >> libstdc++-v3/ChangeLog: >> >> * include/std/mdspan (mdspan): New class. >> * src/c++23/std.cc.in (mdspan): Add. >> * 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> >> --- >> > Only two small comments: > - use of header guard instead of pragma > - use of md.empty() instead of md.extents(0) > I will do these changes locally. > >> libstdc++-v3/include/std/mdspan | 284 +++++++++ >> libstdc++-v3/src/c++23/std.cc.in | 3 +- >> .../23_containers/mdspan/class_mandate_neg.cc | 41 ++ >> .../23_containers/mdspan/layout_like.h | 80 +++ >> .../testsuite/23_containers/mdspan/mdspan.cc | 603 ++++++++++++++++++ >> .../23_containers/mdspan/out_of_bounds_neg.cc | 24 + >> 6 files changed, 1034 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 >> create mode 100644 >> libstdc++-v3/testsuite/23_containers/mdspan/out_of_bounds_neg.cc >> >> diff --git a/libstdc++-v3/include/std/mdspan >> b/libstdc++-v3/include/std/mdspan >> index 7e970c2b905..f64804e2a42 100644 >> --- a/libstdc++-v3/include/std/mdspan >> +++ b/libstdc++-v3/include/std/mdspan >> @@ -1057,6 +1057,290 @@ _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 (sizeof...(__idxs) > 0) >> + __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::__size(extents())); >> + } >> + >> + [[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, >> + extents<size_t, >> __mdspan::__dynamic_extent<_Integrals>()...>>; >> + >> + 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..de19b6d85c9 >> --- /dev/null >> +++ b/libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc >> @@ -0,0 +1,41 @@ >> +// { 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()); >> + >> +// { 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..fc239d65411 >> --- /dev/null >> +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h >> @@ -0,0 +1,80 @@ >> +#pragma once >> > Please use header-guard instead of checking empty. > >> + >> +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 >> + { >> + for (size_t i = 0; i < extents_type::rank(); ++i) >> + if (m_exts.extent(i) == 0) >> + return 0; >> + return 1; >> + } >> + >> + template<typename... Indices> >> + requires (sizeof...(Indices) == extents_type::rank()) >> + constexpr index_type >> + operator()(Indices...) const noexcept >> + { return 0; } >> + >> + static constexpr index_type >> + stride(rank_type) noexcept >> + { return 0; } >> + >> + static constexpr bool >> + is_always_unique() noexcept >> + { return false; } >> + >> + static constexpr bool >> + is_always_exhaustive() noexcept >> + { return true; } >> + >> + static constexpr bool >> + is_always_strided() noexcept >> + { return true; } >> + >> + constexpr bool >> + is_unique() noexcept >> + { >> + if (required_span_size() == 0) >> + return true; >> + >> + for (size_t i = 0; i < extents_type::rank(); ++i) >> + if (m_exts.extent(i) > 1) >> + return false; >> + return true; >> + } >> + >> + static constexpr bool >> + is_exhaustive() noexcept >> + { return true; } >> + >> + static constexpr bool >> + is_strided() noexcept >> + { return true; } >> + >> + 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..ba67cb00e1f >> --- /dev/null >> +++ b/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc >> @@ -0,0 +1,603 @@ >> +// { 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); >> > I would check md.empty() instead of checking extent here. I think on > mdspan level, > this is more interesting to check. > >> + 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>>>); >> + >> + static_assert(std::is_convertible_v< >> + std::mdspan<double, std::extents<int, 2>>, >> + std::mdspan<const double, std::extents<int, 2>>>); >> + >> + static_assert(!std::is_constructible_v< >> + std::mdspan<double, std::extents<int, 2>>, >> + std::mdspan<const 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, >> 1>>); >> + static_assert(!std::is_constructible_v< >> + std::mdspan<float, std::dextents<int, 2>>, float*, std::span<int, >> 3>>); >> + 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, bool NothrowConstructible = true, >> + bool NothrowAssignable = true> >> + class OpaqueAccessor >> + { >> + struct Handle >> + { >> + constexpr >> + Handle(T * other) >> + : ptr(other) >> + { } >> + >> + constexpr >> + Handle(const Handle&) noexcept(NothrowConstructible) = default; >> + >> + constexpr >> + Handle(Handle&&) noexcept(NothrowConstructible) = default; >> + >> + constexpr Handle& >> + operator=(const Handle&) noexcept(NothrowAssignable) = default; >> + >> + constexpr Handle& >> + operator=(Handle&&) noexcept(NothrowAssignable) = default; >> + >> + 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; >> +} >> + >> +template<bool Constructible, bool Assignable> >> +constexpr void >> +test_nothrow_movable() >> +{ >> + using Layout = std::layout_left; >> + using Extents = std::dextents<int, 3>; >> + using Accessor = OpaqueAccessor<int, Constructible, Assignable>; >> + using Handle = Accessor::data_handle_type; >> + static_assert(std::is_nothrow_move_assignable_v<Accessor>); >> + static_assert(std::is_nothrow_move_constructible_v<Accessor>); >> + static_assert(std::is_nothrow_move_assignable_v<Handle> == Assignable); >> + static_assert(std::is_nothrow_move_constructible_v<Handle> == >> Constructible); >> + >> + using MDSpan = std::mdspan<int, Extents, Layout, Accessor>; >> + static_assert(std::is_nothrow_move_assignable_v<MDSpan> == Assignable); >> + static_assert(std::is_nothrow_move_constructible_v<MDSpan> == >> Constructible); >> +} >> + >> +constexpr void >> +test_nothrow_movable_all() >> +{ >> + using MDSpan = std::mdspan<double, std::dextents<int, 3>>; >> + static_assert(std::is_nothrow_move_assignable_v<MDSpan>); >> + static_assert(std::is_nothrow_move_constructible_v<MDSpan>); >> + >> + test_nothrow_movable<true, true>(); >> + test_nothrow_movable<true, false>(); >> + test_nothrow_movable<false, true>(); >> + test_nothrow_movable<false, false>(); >> +} >> + >> +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()); >> + >> + test_nothrow_movable_all(); >> + return 0; >> +} >> diff --git >> a/libstdc++-v3/testsuite/23_containers/mdspan/out_of_bounds_neg.cc >> b/libstdc++-v3/testsuite/23_containers/mdspan/out_of_bounds_neg.cc >> new file mode 100644 >> index 00000000000..dceae56e9d9 >> --- /dev/null >> +++ b/libstdc++-v3/testsuite/23_containers/mdspan/out_of_bounds_neg.cc >> @@ -0,0 +1,24 @@ >> +// { dg-do compile { target c++23 } } >> +#include<mdspan> >> + >> +#include "layout_like.h" >> + >> +template<typename Layout> >> +constexpr bool >> +test_invalid_multi_index() >> +{ >> + >> + 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 "non-constant condition" } >> +// { dg-prune-output "__glibcxx_assert" } >> -- >> 2.49.0 >> >>