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. Signed-off-by: Luc Grosheintz <luc.groshei...@gmail.com> --- libstdc++-v3/include/std/mdspan | 253 +++++++++++++ libstdc++-v3/src/c++23/std.cc.in | 3 +- .../23_containers/mdspan/class_mandate_neg.cc | 32 ++ .../testsuite/23_containers/mdspan/mdspan.cc | 348 ++++++++++++++++++ 4 files changed, 635 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/mdspan.cc diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan index 8c6b7f49155..6d7da858032 100644 --- a/libstdc++-v3/include/std/mdspan +++ b/libstdc++-v3/include/std/mdspan @@ -1030,6 +1030,259 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return __p + __i; } }; + template<typename _ElementType, typename _Extents, + typename _LayoutPolicy = layout_right, + typename _AccessorPolicy = default_accessor<_ElementType>> + class mdspan { + 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 __index = _M_mapping( + static_cast<index_type>(std::move(__indices))...); + __glibcxx_assert(__index < _M_mapping.required_span_size()); + 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)[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 + { return size_type(__mdspan::__fwd_prod(extents(), rank())); } + + [[nodiscard]] constexpr bool + empty() const noexcept + { + return __mdspan::__contains_zero(__mdspan::__static_extents<_Extents>()) + || __mdspan::__contains_zero(__mdspan::__dynamic_extents(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)>>; + + 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 51111627d7d..bad935cc045 100644 --- a/libstdc++-v3/src/c++23/std.cc.in +++ b/libstdc++-v3/src/c++23/std.cc.in @@ -1848,7 +1848,8 @@ export namespace std using std::layout_right; using std::layout_stride; using std::default_accessor; - // FIXME layout_left_padded, layout_right_padded and mdspan + using std::mdspan; + // FIXME layout_left_padded, layout_right_padded, 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..55c49fa7018 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/class_mandate_neg.cc @@ -0,0 +1,32 @@ +// { dg-do compile { target c++23 } } +#include<mdspan> + +#include <cstdint> + +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; } +}; + +struct A +{ + std::mdspan<double, ExtentsLike> md1; // { dg-error "required from here" } +}; + +struct B +{ + using E = std::extents<int, 1>; + using L = std::layout_right; + using A = std::default_accessor<double>; + + std::mdspan<float, E, L, A> md2; // { dg-error "required from here" } +}; + +// { dg-prune-output "no type named '_S_storage'" } +// { dg-prune-output "non-constant condition" } +// { dg-prune-output "static assertion failed" } 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..7fcc50d7cb7 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc @@ -0,0 +1,348 @@ +// { dg-do run { target c++23 } } +#include <mdspan> + +#include <testsuite_hooks.h> + +constexpr auto dyn = std::dynamic_extent; + +template<typename T, typename E, typename L, typename A> + constexpr void + test_typedefs() + { + using MDSpan = std::mdspan<T, E, L, A>; + using MDConstSpan = std::mdspan<const T, E, L>; + 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, T>); + static_assert(std::same_as<typename MDConstSpan::element_type, const T>); + static_assert(std::same_as<typename MDConstSpan::value_type, 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>); + } + +constexpr void + test_typedefs_all() +{ + test_typedefs<double, std::extents<int, 1, 2>, std::layout_left, std::default_accessor<double>>(); +} + +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 MDSpan> + constexpr void + test_static_extent() + { + using Extents = typename MDSpan::extents_type; + for(size_t i = 0; i < MDSpan::rank(); ++i) + VERIFY(MDSpan::static_extent(i) == Extents::static_extent(i)); + } + +constexpr bool +test_static_extent_all() +{ + test_static_extent<std::mdspan<double, std::extents<int>>>(); + test_static_extent<std::mdspan<double, std::extents<int, 1>>>(); + test_static_extent<std::mdspan<double, 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()); + 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) +{ + using MDSpan = decltype(md); + static_assert(std::same_as<typename MDSpan::element_type, T>); + 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, std::default_accessor<T>>); +} + +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}; + + 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(), std::span<const int, 2>(shape))); + + 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; +} + +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); + + 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_default_ctor(); + static_assert(test_default_ctor()); + + test_rank_all(); + test_static_extent_all(); + static_assert(test_static_extent_all()); + + test_class_properties_all(); + static_assert(test_class_properties_all()); + + test_empty_all(); + static_assert(test_empty_all()); + + 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_access(); + static_assert(test_access()); + + test_swap(); + static_assert(test_swap()); + return 0; +} -- 2.49.0