From: Luc Grosheintz Date: Tue, 8 Jul 2025 08:24:26 +0000 (+0200) Subject: libstdc++: Implement mdspan and tests [PR107761]. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b7b8eb90abaeaaf4a51325e087cd43a4dac8d25a;p=thirdparty%2Fgcc.git libstdc++: Implement mdspan and tests [PR107761]. 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 . 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. Reviewed-by: Tomasz KamiƄski Signed-off-by: Luc Grosheintz --- diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan index b0d8088bb77..5a42aead3eb 100644 --- a/libstdc++-v3/include/std/mdspan +++ b/libstdc++-v3/include/std/mdspan @@ -1057,6 +1057,291 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return __p + __i; } }; + namespace __mdspan + { + template + 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> + 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; + using element_type = _ElementType; + using value_type = remove_cv_t; + 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 + && is_default_constructible_v + && is_default_constructible_v + : _M_accessor(), _M_mapping(), _M_handle() + { } + + constexpr + mdspan(const mdspan& __other) = default; + + constexpr + mdspan(mdspan&& __other) = default; + + template<__mdspan::__valid_index_type... _OIndexTypes> + requires (sizeof...(_OIndexTypes) == rank() + || sizeof...(_OIndexTypes) == rank_dynamic()) + && is_constructible_v + && is_default_constructible_v + constexpr explicit + mdspan(data_handle_type __handle, _OIndexTypes... __exts) + : _M_accessor(), + _M_mapping(_Extents(static_cast(std::move(__exts))...)), + _M_handle(std::move(__handle)) + { } + + template<__mdspan::__valid_index_type _OIndexType, + size_t _Nm> + requires (_Nm == rank() || _Nm == rank_dynamic()) + && is_constructible_v + && is_default_constructible_v + 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 _OIndexType, + size_t _Nm> + requires (_Nm == rank() || _Nm == rank_dynamic()) + && is_constructible_v + && is_default_constructible_v + 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 + && is_default_constructible_v + : _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 + : _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 + requires is_constructible_v&> + && is_constructible_v + constexpr explicit(!is_convertible_v< + const typename _OLayout::mapping<_OExtents>&, mapping_type> + || !is_convertible_v) + 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); + static_assert(is_constructible_v); + } + + constexpr mdspan& + operator=(const mdspan& __other) = default; + + constexpr mdspan& + operator=(mdspan&& __other) = default; + + template<__mdspan::__valid_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({__idxs...}))); + return _M_mapping(__idxs...); + }; + + auto __index = __checked_call( + static_cast(std::move(__indices))...); + return _M_accessor.access(_M_handle, __index); + } + + template<__mdspan::__valid_index_type _OIndexType> + constexpr reference + operator[](span<_OIndexType, rank()> __indices) const + { + auto __call = [&](index_sequence<_Counts...>) + -> reference + { return (*this)[index_type(as_const(__indices[_Counts]))...]; }; + return __call(make_index_sequence()); + } + + template<__mdspan::__valid_index_type _OIndexType> + constexpr reference + operator[](const array<_OIndexType, rank()>& __indices) const + { return (*this)[span(__indices)]; } + + constexpr size_type + size() const noexcept + { + __glibcxx_assert(cmp_less_equal(_M_mapping.required_span_size(), + numeric_limits::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 + { + using std::swap; + swap(__x._M_mapping, __y._M_mapping); + swap(__x._M_accessor, __y._M_accessor); + 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 + requires is_array_v<_CArray> && (rank_v<_CArray> == 1) + mdspan(_CArray&) + -> mdspan, + extents>>; + + template + requires is_pointer_v> + mdspan(_Pointer&&) + -> mdspan>, extents>; + + template + requires (is_convertible_v<_Integrals, size_t> && ...) + && (sizeof...(_Integrals) > 0) + explicit mdspan(_ElementType*, _Integrals...) + -> mdspan<_ElementType, + extents()...>>; + + template + mdspan(_ElementType*, span<_OIndexType, _Nm>) + -> mdspan<_ElementType, dextents>; + + template + mdspan(_ElementType*, const array<_OIndexType, _Nm>&) + -> mdspan<_ElementType, dextents>; + + template + mdspan(_ElementType*, const extents<_IndexType, _ExtentsPack...>&) + -> mdspan<_ElementType, extents<_IndexType, _ExtentsPack...>>; + + template + mdspan(_ElementType*, const _MappingType&) + -> mdspan<_ElementType, typename _MappingType::extents_type, + typename _MappingType::layout_type>; + + template + mdspan(const typename _AccessorType::data_handle_type&, const _MappingType&, + const _AccessorType&) + -> mdspan; + _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 86e6d88904b..dd05a839a92 100644 --- a/libstdc++-v3/src/c++23/std.cc.in +++ b/libstdc++-v3/src/c++23/std.cc.in @@ -1846,7 +1846,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 + +#include +#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 md1; // { dg-error "required from here" } + return true; +} +static_assert(test_custom_extents_type()); + +constexpr bool +test_element_type_mismatch() +{ + using E = std::extents; + using L = std::layout_right; + using A = std::default_accessor; + + [[maybe_unused]] std::mdspan 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..6a0f8cafaa7 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layout_like.h @@ -0,0 +1,83 @@ +#ifndef TEST_MDSPAN_LAYOUT_LIKE_H +#define TEST_MDSPAN_LAYOUT_LIKE_H 1 + +struct LayoutLike +{ + template + 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 + 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; + }; +}; + +#endif 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..9252273bf66 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc @@ -0,0 +1,643 @@ +// { dg-do run { target c++23 } } +#include + +#include +#include "extents/int_like.h" +#include "layout_like.h" + +constexpr auto dyn = std::dynamic_extent; + +template> + constexpr void + assert_typedefs() + { + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as>); + static_assert(std::same_as); + static_assert(std::same_as>); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + } + +template typename A> + constexpr void + test_typedefs() + { assert_typedefs>, T, E, L, A>(); } + +constexpr void +test_typedefs_all() +{ + using E = std::extents; + using L = std::layout_left; + + test_typedefs(); + test_typedefs(); +} + +template + 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>>(); + test_rank>>(); + test_rank>>(); + return true; +} + +template + 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{}); + test_extent(std::extents{}); + test_extent(std::extents{}); + return true; +} + +template + constexpr void + test_class_properties() + { + static_assert(std::copyable); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_swappable_v); + constexpr bool trivially_copyable = + std::is_trivially_copyable_v + && std::is_trivially_copyable_v + && std::is_trivially_copyable_v; + static_assert(std::is_trivially_copyable_v == trivially_copyable); + } + +constexpr bool +test_class_properties_all() +{ + test_class_properties>>(); + test_class_properties>>(); + test_class_properties>>(); + return true; +} + +constexpr bool +test_default_ctor() +{ + static_assert(!std::is_default_constructible_v>>); + static_assert(!std::is_default_constructible_v>>); + static_assert(std::is_default_constructible_v>>); + + std::mdspan> md; + VERIFY(md.data_handle() == nullptr); + VERIFY(md.empty()); + return true; +} + +constexpr bool +test_from_other() +{ + using Extents = std::extents; + auto exts = Extents{}; + + auto mapping = std::layout_right::mapping(exts); + constexpr size_t n = mapping.required_span_size(); + std::array storage{}; + + auto md1 = std::mdspan(storage.data(), exts); + auto md2 = std::mdspan>(md1); + + VERIFY(md1.data_handle() == md2.data_handle()); + VERIFY(md1.size() == md2.size()); + + static_assert(!std::is_convertible_v< + std::mdspan>, + std::mdspan>>); + + static_assert(std::is_convertible_v< + std::mdspan>, + std::mdspan>>); + + static_assert(!std::is_constructible_v< + std::mdspan>, + std::mdspan>>); + + return true; +} + +template> + constexpr void + assert_deduced_typedefs(auto md) + { assert_typedefs(); } + +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>(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>(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 data{1.1, 2.2, 3.3, 4.4, 5.5, 6.6}; + std::array shape{2, 3}; + std::span shape_view(shape); + + auto verify = [&data](auto md) + { + assert_deduced_typedefs>(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> md1 = {data.data(), shape}; + verify(md1); + + std::mdspan> md2 = {data.data(), shape_view}; + verify(md2); + + static_assert(std::is_constructible_v< + std::mdspan>, float*>); + static_assert(!std::is_constructible_v< + std::mdspan>, float*, int>); + static_assert(std::is_constructible_v< + std::mdspan>, float*, int, int>); + static_assert(std::is_constructible_v< + std::mdspan>, float*, std::span>); + static_assert(std::is_constructible_v< + std::mdspan>, float*, std::span>); + static_assert(!std::is_convertible_v< + float*, std::mdspan>>); + + static_assert(std::is_constructible_v< + std::mdspan>, float*, std::span>); + static_assert(!std::is_constructible_v< + std::mdspan>, float*, std::span>); + static_assert(!std::is_constructible_v< + std::mdspan>, float*, std::span>); + static_assert(!std::is_constructible_v< + std::mdspan>, float*, std::span>); + return true; +} + +constexpr bool +test_from_extents() +{ + constexpr size_t n = 3*5*7; + std::array storage{}; + using Extents = std::extents; + auto exts = Extents{}; + auto md = std::mdspan(storage.data(), exts); + + assert_deduced_typedefs(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 storage{}; + using Extents = std::extents; + + auto exts = Extents{}; + auto m = std::layout_left::mapping(exts); + auto md = std::mdspan(storage.data(), m); + + assert_deduced_typedefs(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 storage{}; + using Extents = std::extents; + + auto exts = Extents{}; + auto m = std::layout_left::mapping(exts); + auto a = std::default_accessor{}; + auto md = std::mdspan(storage.data(), m, a); + + assert_deduced_typedefs(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 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{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{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 + 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 storage{}; + using Extents = std::extents; + + auto exts = Extents{}; + auto m = std::layout_left::mapping(exts); + auto a = OpaqueAccessor{}; + auto handle = OpaqueAccessor::data_handle_type{storage.data()}; + auto md = std::mdspan(handle, m, a); + + using MDSpan = decltype(md); + static_assert(std::same_as); + + 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 + 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 + 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 storage{}; + using Extents = std::extents; + + auto exts = Extents{}; + auto m = std::layout_left::mapping(exts); + auto a = BaseClassAccessor{}; + auto md = std::mdspan(storage.data(), m, a); + + using MDSpan = decltype(md); + static_assert(std::same_as); + static_assert(std::same_as); + 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>{}; + auto md = std::mdspan(&data, m); + VERIFY((md[0, 0, 0]) == data); + VERIFY((md[0, 1, 2]) == data); + return true; +} + +template + constexpr void + test_empty(MDSpan md) + { + VERIFY(md.empty() == (md.size() == 0)); + } + +constexpr bool +test_empty_all() +{ + test_empty(std::mdspan>{}); + return true; +} + +constexpr bool +test_access() +{ + using Extents = std::extents; + auto exts = Extents{}; + + auto mapping = std::layout_left::mapping(exts); + constexpr size_t n = mapping.required_span_size(); + std::array storage{}; + + auto md = std::mdspan(storage.data(), mapping); + static_assert(std::__mdspan::__mapping_alike); + + 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 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; + auto e1 = Extents{3, 5}; + auto e2 = Extents{7, 11}; + + std::array s1{}; + std::array 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; +} + +namespace adl +{ + template + struct SwappableAccessor + { + using element_type = T; + using reference = T&; + using data_handle_type = T*; + using offset_policy = SwappableAccessor; + + 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 p + i; } + + friend void + swap(SwappableAccessor&, SwappableAccessor&) + { ++swap_count; } + + static inline size_t swap_count = 0; + }; +} + +void +test_swap_adl() +{ + using Extents = std::extents; + using Layout = std::layout_left; + using Accessor = adl::SwappableAccessor; + Accessor::swap_count = 0; + + std::mdspan m1, m2; + swap(m1, m2); + VERIFY(Accessor::swap_count == 1); +} + +template +constexpr void +test_nothrow_movable() +{ + using Layout = std::layout_left; + using Extents = std::dextents; + using Accessor = OpaqueAccessor; + using Handle = Accessor::data_handle_type; + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v == Assignable); + static_assert(std::is_nothrow_move_constructible_v == Constructible); + + using MDSpan = std::mdspan; + static_assert(std::is_nothrow_move_assignable_v == Assignable); + static_assert(std::is_nothrow_move_constructible_v == Constructible); +} + +constexpr void +test_nothrow_movable_all() +{ + using MDSpan = std::mdspan>; + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_move_constructible_v); + + test_nothrow_movable(); + test_nothrow_movable(); + test_nothrow_movable(); + test_nothrow_movable(); +} + +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_swap_adl(); + + 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 + +#include "layout_like.h" + +template +constexpr bool +test_invalid_multi_index() +{ + + double data = 1.1; + auto m = typename Layout::mapping>{}; + 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()); // { dg-error "expansion of" } +static_assert(test_invalid_multi_index()); // { dg-error "expansion of" } +static_assert(test_invalid_multi_index()); // { dg-error "expansion of" } +static_assert(test_invalid_multi_index()); // { dg-error "expansion of" } + +// { dg-prune-output "non-constant condition" } +// { dg-prune-output "__glibcxx_assert" }