From ab7bb2375dec29d33c2f7add04f8ba3a53fea430 Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Sat, 10 Jun 2023 18:22:43 +0200 Subject: [PATCH] ``: Test `mdspan` (#3749) Co-authored-by: Stephan T. Lavavej --- stl/inc/mdspan | 31 +- tests/std/include/test_mdspan_support.hpp | 60 +- tests/std/test.lst | 1 + tests/std/tests/P0009R18_mdspan/test.cpp | 4 +- .../P0009R18_mdspan_layout_left/test.cpp | 24 +- .../P0009R18_mdspan_layout_right/test.cpp | 24 +- .../P0009R18_mdspan_layout_stride/test.cpp | 24 +- .../std/tests/P0009R18_mdspan_mdspan/env.lst | 4 + .../std/tests/P0009R18_mdspan_mdspan/test.cpp | 1352 +++++++++++++++++ 9 files changed, 1439 insertions(+), 85 deletions(-) create mode 100644 tests/std/tests/P0009R18_mdspan_mdspan/env.lst create mode 100644 tests/std/tests/P0009R18_mdspan_mdspan/test.cpp diff --git a/stl/inc/mdspan b/stl/inc/mdspan index 50b0b055fbe..2a0f9b289ab 100644 --- a/stl/inc/mdspan +++ b/stl/inc/mdspan @@ -952,7 +952,7 @@ public: && is_nothrow_constructible_v && (_Size == rank() || _Size == rank_dynamic()) && is_constructible_v && is_default_constructible_v - constexpr explicit(_Size != rank_dynamic()) mdspan(data_handle_type _Ptr_, span<_OtherIndexType, _Size>& _Exts) + constexpr explicit(_Size != rank_dynamic()) mdspan(data_handle_type _Ptr_, span<_OtherIndexType, _Size> _Exts) : _Ptr(_STD move(_Ptr_)), _Map(extents_type{_Exts}), _Acc() {} template @@ -982,7 +982,7 @@ public: !is_convertible_v&, mapping_type> || !is_convertible_v) mdspan(const mdspan<_OtherElementType, _OtherExtents, _OtherLayoutPolicy, _OtherAccessor>& _Other) - : _Ptr(_Other._Ptr), _Map(_Other._Map), _Acc(_Other._Acc) { + : _Ptr(_Other.data_handle()), _Map(_Other.mapping()), _Acc(_Other.accessor()) { static_assert(is_constructible_v, "The data_handle_type must be constructible from const typename OtherAccessor::data_handle_type& (N4950 " "[mdspan.mdspan.cons]/20.1)."); @@ -993,7 +993,7 @@ public: constexpr mdspan& operator=(const mdspan&) = default; constexpr mdspan& operator=(mdspan&&) = default; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 template requires (is_convertible_v<_OtherIndexTypes, index_type> && ...) && (is_nothrow_constructible_v && ...) @@ -1001,7 +1001,7 @@ public: _NODISCARD constexpr reference operator[](_OtherIndexTypes... _Indices) const { return _Acc.access(_Ptr, static_cast(_Map(static_cast(_STD move(_Indices))...))); } -#endif // __clang__ +#endif // __cpp_multidimensional_subscript template requires is_convertible_v @@ -1032,9 +1032,9 @@ public: } friend constexpr void swap(mdspan& _Left, mdspan& _Right) noexcept { - swap(_Left._Ptr, _Right._Ptr); - swap(_Left._Map, _Right._Map); - swap(_Left._Acc, _Right._Acc); + swap(_Left._Ptr, _Right._Ptr); // intentional ADL + swap(_Left._Map, _Right._Map); // intentional ADL + swap(_Left._Acc, _Right._Acc); // intentional ADL } _NODISCARD constexpr const extents_type& extents() const noexcept { @@ -1088,13 +1088,20 @@ public: private: template _NODISCARD constexpr reference _Index_impl(span<_OtherIndexType, rank()> _Indices, index_sequence<_Seq...>) const { -#ifdef __clang__ // TRANSITION, P2128R6 - return this->operator[](_STD as_const(_Indices[_Seq])...); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv - return _Acc.access(_Ptr, static_cast(_Map(static_cast(_STD as_const(_Indices[_Seq]))...))); -#endif // ^^^ !defined(__clang__) ^^^ +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 + return operator[](_STD as_const(_Indices[_Seq])...); +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv + return _Multidimensional_access(_STD as_const(_Indices[_Seq])...); +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } +#ifndef __cpp_multidimensional_subscript // TRANSITION, P2128R6 + template + _NODISCARD constexpr reference _Multidimensional_access(_OtherIndexTypes... _Indices) const { + return _Acc.access(_Ptr, static_cast(_Map(static_cast(_STD move(_Indices))...))); + } +#endif // __cpp_multidimensional_subscript + data_handle_type _Ptr{}; mapping_type _Map{}; accessor_type _Acc{}; diff --git a/tests/std/include/test_mdspan_support.hpp b/tests/std/include/test_mdspan_support.hpp index 9ac83834803..23b8902dc45 100644 --- a/tests/std/include/test_mdspan_support.hpp +++ b/tests/std/include/test_mdspan_support.hpp @@ -27,12 +27,10 @@ namespace detail { constexpr void check_implicit_conversion(T); // not defined } -// clang-format off template -concept NotImplicitlyConstructibleFrom = - std::constructible_from - && !requires(Args&&... args) { detail::check_implicit_conversion({std::forward(args)...}); }; -// clang-format on +concept NotImplicitlyConstructibleFrom = std::constructible_from && !requires(Args&&... args) { + detail::check_implicit_conversion({std::forward(args)...}); +}; namespace detail { template @@ -51,17 +49,14 @@ namespace detail { && std::same_as && is_mapping_of_v; - // clang-format off template - concept CheckMemberFunctionsOfLayoutMapping = - requires(const M m) { - { m.extents() } -> std::same_as; - { m.required_span_size() } -> std::same_as; - { m.is_unique() } -> std::same_as; - { m.is_exhaustive() } -> std::same_as; - { m.is_strided() } -> std::same_as; - }; - // clang-format on + concept CheckMemberFunctionsOfLayoutMapping = requires(const M m) { + { m.extents() } -> std::same_as; + { m.required_span_size() } -> std::same_as; + { m.is_unique() } -> std::same_as; + { m.is_exhaustive() } -> std::same_as; + { m.is_strided() } -> std::same_as; + }; template concept CheckStaticFunctionsOfLayoutMapping = requires { @@ -74,14 +69,11 @@ namespace detail { }; } // namespace detail -// clang-format off template -concept CheckCallOperatorOfLayoutMapping = - requires(const M m, Indices... i) { - { m(i...) } -> std::same_as; - { m(i...) == m(static_cast(i)...) } -> std::same_as; - }; -// clang-format on +concept CheckCallOperatorOfLayoutMapping = requires(const M m, Indices... i) { + { m(i...) } -> std::same_as; + { m(i...) == m(static_cast(i)...) } -> std::same_as; +}; template concept CheckStrideMemberFunction = requires(M mapping, M::rank_type i) { @@ -127,23 +119,21 @@ namespace detail { template concept CheckNestedTypesOfAccessorPolicy = sizeof(typename A::element_type) > 0 - && (!std::is_abstract_v) &&std::copyable&& std:: - is_nothrow_move_constructible_v&& std::is_nothrow_move_assignable_v< - typename A::data_handle_type>&& std::is_nothrow_swappable_v&& std:: - common_reference_with + && !std::is_abstract_v && std::copyable + && std::is_nothrow_move_constructible_v + && std::is_nothrow_move_assignable_v + && std::is_nothrow_swappable_v + && std::common_reference_with && (std::same_as || check_accessor_policy_requirements()) - && std::constructible_from&& std::is_same_v; + && std::constructible_from + && std::is_same_v; - // clang-format off template - concept CheckMemberFunctionsOfAccessorPolicy = - requires(const A a, const A::data_handle_type p, size_t i) { - { a.access(p, i) } -> std::same_as; - { a.offset(p, i) } -> std::same_as; - }; - // clang-format on + concept CheckMemberFunctionsOfAccessorPolicy = requires(const A a, const A::data_handle_type p, size_t i) { + { a.access(p, i) } -> std::same_as; + { a.offset(p, i) } -> std::same_as; + }; } // namespace detail template diff --git a/tests/std/test.lst b/tests/std/test.lst index da65c04701e..018e7f0a004 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -243,6 +243,7 @@ tests\P0009R18_mdspan_layout_right tests\P0009R18_mdspan_layout_right_death tests\P0009R18_mdspan_layout_stride tests\P0009R18_mdspan_layout_stride_death +tests\P0009R18_mdspan_mdspan tests\P0019R8_atomic_ref tests\P0024R2_parallel_algorithms_adjacent_difference tests\P0024R2_parallel_algorithms_adjacent_find diff --git a/tests/std/tests/P0009R18_mdspan/test.cpp b/tests/std/tests/P0009R18_mdspan/test.cpp index dcd3274a722..28ea7b5acae 100644 --- a/tests/std/tests/P0009R18_mdspan/test.cpp +++ b/tests/std/tests/P0009R18_mdspan/test.cpp @@ -1029,10 +1029,10 @@ void mdspan_tests_observers() { static_assert(!mds.is_exhaustive()); static_assert(mds.is_strided()); -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 static_assert(mds[1, 0] == 1); static_assert(mds[1, 2] == 7); -#endif // __clang__ +#endif // __cpp_multidimensional_subscript static_assert(mds[array{0, 1}] == 3); static_assert(mds[array{1, 1}] == 4); diff --git a/tests/std/tests/P0009R18_mdspan_layout_left/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_left/test.cpp index 4e2e247b861..d585c455b10 100644 --- a/tests/std/tests/P0009R18_mdspan_layout_left/test.cpp +++ b/tests/std/tests/P0009R18_mdspan_layout_left/test.cpp @@ -302,57 +302,57 @@ constexpr void check_correctness() { const array values{0, 1, 2}; mdspan, layout_left> vec{values.data()}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert(vec[0] == 0); assert(vec[1] == 1); assert(vec[2] == 2); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert(vec[array{0}] == 0); assert(vec[array{1}] == 1); assert(vec[array{2}] == 2); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } { // 3x2 matrix with column-major order const array values{0, 1, 2, 3, 4, 5}; mdspan, layout_left> matrix{values.data()}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((matrix[0, 0] == 0)); assert((matrix[1, 0] == 1)); assert((matrix[2, 0] == 2)); assert((matrix[0, 1] == 3)); assert((matrix[1, 1] == 4)); assert((matrix[2, 1] == 5)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((matrix[array{0, 0}] == 0)); assert((matrix[array{1, 0}] == 1)); assert((matrix[array{2, 0}] == 2)); assert((matrix[array{0, 1}] == 3)); assert((matrix[array{1, 1}] == 4)); assert((matrix[array{2, 1}] == 5)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } { // 3x2x4 tensor const array values{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; mdspan, layout_left> tensor{values.data(), 3, 2, 4}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((tensor[0, 0, 0] == 0)); assert((tensor[2, 0, 0] == 2)); assert((tensor[1, 1, 1] == 10)); assert((tensor[0, 0, 3] == 18)); assert((tensor[2, 1, 2] == 17)); assert((tensor[2, 1, 3] == 23)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((tensor[array{0, 0, 0}] == 0)); assert((tensor[array{2, 0, 0}] == 2)); assert((tensor[array{1, 1, 1}] == 10)); assert((tensor[array{0, 0, 3}] == 18)); assert((tensor[array{2, 1, 2}] == 17)); assert((tensor[array{2, 1, 3}] == 23)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } { // 2x3x2x3 tensor @@ -360,7 +360,7 @@ constexpr void check_correctness() { 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; mdspan, layout_left> tensor{values.data(), 2, 3}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((tensor[0, 0, 0, 0] == 0)); assert((tensor[1, 0, 0, 0] == 1)); assert((tensor[0, 1, 1, 0] == 8)); @@ -368,7 +368,7 @@ constexpr void check_correctness() { assert((tensor[0, 0, 0, 2] == 24)); assert((tensor[0, 2, 0, 2] == 28)); assert((tensor[1, 2, 1, 2] == 35)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((tensor[array{0, 0, 0, 0}] == 0)); assert((tensor[array{1, 0, 0, 0}] == 1)); assert((tensor[array{0, 1, 1, 0}] == 8)); @@ -376,7 +376,7 @@ constexpr void check_correctness() { assert((tensor[array{0, 0, 0, 2}] == 24)); assert((tensor[array{0, 2, 0, 2}] == 28)); assert((tensor[array{1, 2, 1, 2}] == 35)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } } diff --git a/tests/std/tests/P0009R18_mdspan_layout_right/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_right/test.cpp index 67530019c1b..dd67099a7a1 100644 --- a/tests/std/tests/P0009R18_mdspan_layout_right/test.cpp +++ b/tests/std/tests/P0009R18_mdspan_layout_right/test.cpp @@ -303,43 +303,43 @@ constexpr void check_correctness() { const array vals{2, 1, 0}; mdspan, layout_right> vec{vals.data()}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert(vec[0] == 2); assert(vec[1] == 1); assert(vec[2] == 0); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert(vec[array{0}] == 2); assert(vec[array{1}] == 1); assert(vec[array{2}] == 0); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } { // 4x3 matrix with row-major order const array vals{11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; mdspan, layout_right> matrix{vals.data()}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((matrix[0, 0] == 11)); assert((matrix[0, 2] == 9)); assert((matrix[1, 1] == 7)); assert((matrix[2, 0] == 5)); assert((matrix[2, 2] == 3)); assert((matrix[3, 1] == 1)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((matrix[array{0, 0}] == 11)); assert((matrix[array{0, 2}] == 9)); assert((matrix[array{1, 1}] == 7)); assert((matrix[array{2, 0}] == 5)); assert((matrix[array{2, 2}] == 3)); assert((matrix[array{3, 1}] == 1)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } { // 4x3x2 tensor const array vals{23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; mdspan, layout_right> tensor{vals.data(), 4, 3, 2}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((tensor[0, 0, 0] == 23)); assert((tensor[0, 0, 1] == 22)); assert((tensor[0, 1, 0] == 21)); @@ -350,7 +350,7 @@ constexpr void check_correctness() { assert((tensor[1, 1, 1] == 14)); assert((tensor[2, 2, 1] == 6)); assert((tensor[3, 2, 1] == 0)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((tensor[array{0, 0, 0}] == 23)); assert((tensor[array{0, 0, 1}] == 22)); assert((tensor[array{0, 1, 0}] == 21)); @@ -361,7 +361,7 @@ constexpr void check_correctness() { assert((tensor[array{1, 1, 1}] == 14)); assert((tensor[array{2, 2, 1}] == 6)); assert((tensor[array{3, 2, 1}] == 0)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } { // 3x2x3x2 tensor @@ -369,7 +369,7 @@ constexpr void check_correctness() { 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; mdspan, layout_right> tensor{vals.data(), 2, 2}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((tensor[0, 0, 0, 0] == 35)); assert((tensor[0, 0, 0, 1] == 34)); assert((tensor[0, 0, 1, 0] == 33)); @@ -388,7 +388,7 @@ constexpr void check_correctness() { assert((tensor[1, 1, 1, 1] == 14)); assert((tensor[2, 0, 2, 0] == 7)); assert((tensor[2, 1, 2, 1] == 0)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((tensor[array{0, 0, 0, 0}] == 35)); assert((tensor[array{0, 0, 0, 1}] == 34)); assert((tensor[array{0, 0, 1, 0}] == 33)); @@ -407,7 +407,7 @@ constexpr void check_correctness() { assert((tensor[array{1, 1, 1, 1}] == 14)); assert((tensor[array{2, 0, 2, 0}] == 7)); assert((tensor[array{2, 1, 2, 1}] == 0)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } } diff --git a/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp index 435d6371777..ec4ea0711a9 100644 --- a/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp +++ b/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp @@ -415,15 +415,15 @@ constexpr void check_correctness() { layout_stride::mapping m{E{}, array{1}}; mdspan, layout_stride> vec{vals.data(), m}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((vec[0] == 1)); assert((vec[1] == 2)); assert((vec[2] == 3)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((vec[array{0}] == 1)); assert((vec[array{1}] == 2)); assert((vec[array{2}] == 3)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } { // 2x3 matrix with row-major order @@ -432,21 +432,21 @@ constexpr void check_correctness() { layout_stride::mapping m{E{}, array{3, 1}}; mdspan matrix{vals.data(), m}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((matrix[0, 0] == 1)); assert((matrix[0, 1] == 2)); assert((matrix[0, 2] == 3)); assert((matrix[1, 0] == 4)); assert((matrix[1, 1] == 5)); assert((matrix[1, 2] == 6)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((matrix[array{0, 0}] == 1)); assert((matrix[array{0, 1}] == 2)); assert((matrix[array{0, 2}] == 3)); assert((matrix[array{1, 0}] == 4)); assert((matrix[array{1, 1}] == 5)); assert((matrix[array{1, 2}] == 6)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } { // 3x2x2 tensor @@ -456,7 +456,7 @@ constexpr void check_correctness() { assert(!m.is_exhaustive()); mdspan tensor{vals.data(), m}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((tensor[0, 0, 0] == 0)); assert((tensor[0, 0, 1] == 6)); assert((tensor[0, 1, 0] == 1)); @@ -469,7 +469,7 @@ constexpr void check_correctness() { assert((tensor[2, 0, 1] == 22)); assert((tensor[2, 1, 0] == 17)); assert((tensor[2, 1, 1] == 23)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((tensor[array{0, 0, 0}] == 0)); assert((tensor[array{0, 0, 1}] == 6)); assert((tensor[array{0, 1, 0}] == 1)); @@ -482,7 +482,7 @@ constexpr void check_correctness() { assert((tensor[array{2, 0, 1}] == 22)); assert((tensor[array{2, 1, 0}] == 17)); assert((tensor[array{2, 1, 1}] == 23)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } { // 2x3x3x2 tensor @@ -493,7 +493,7 @@ constexpr void check_correctness() { assert(m.is_exhaustive()); mdspan tensor{vals.data(), m}; -#ifdef __clang__ // TRANSITION, P2128R6 +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 assert((tensor[0, 0, 0, 0] == 0)); assert((tensor[0, 0, 0, 1] == 9)); assert((tensor[0, 0, 1, 0] == 3)); @@ -512,7 +512,7 @@ constexpr void check_correctness() { assert((tensor[1, 1, 1, 1] == 31)); assert((tensor[0, 2, 2, 0] == 8)); assert((tensor[1, 2, 2, 1] == 35)); -#else // ^^^ defined(__clang__) / !defined(__clang__) vvv +#else // ^^^ defined(__cpp_multidimensional_subscript) / !defined(__cpp_multidimensional_subscript) vvv assert((tensor[array{0, 0, 0, 0}] == 0)); assert((tensor[array{0, 0, 0, 1}] == 9)); assert((tensor[array{0, 0, 1, 0}] == 3)); @@ -531,7 +531,7 @@ constexpr void check_correctness() { assert((tensor[array{1, 1, 1, 1}] == 31)); assert((tensor[array{0, 2, 2, 0}] == 8)); assert((tensor[array{1, 2, 2, 1}] == 35)); -#endif // ^^^ !defined(__clang__) ^^^ +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ } } diff --git a/tests/std/tests/P0009R18_mdspan_mdspan/env.lst b/tests/std/tests/P0009R18_mdspan_mdspan/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_mdspan/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\concepts_latest_matrix.lst diff --git a/tests/std/tests/P0009R18_mdspan_mdspan/test.cpp b/tests/std/tests/P0009R18_mdspan_mdspan/test.cpp new file mode 100644 index 00000000000..a0e16d63392 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_mdspan/test.cpp @@ -0,0 +1,1352 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +class ActionTracker { +public: + constexpr explicit ActionTracker(int id_) noexcept : id{id_} {} + + constexpr ActionTracker(const ActionTracker& other) noexcept : id{other.id}, copy_constructed{true} {} + + constexpr ActionTracker(ActionTracker&& other) noexcept : id{exchange(other.id, -1)}, move_constructed{true} {} + + constexpr ActionTracker& operator=(const ActionTracker& other) noexcept { + id = other.id; + copy_assigned = true; + return *this; + } + + constexpr ActionTracker& operator=(ActionTracker&& other) noexcept { + id = exchange(other.id, -1); + move_assigned = true; + return *this; + } + + constexpr int get_id() const noexcept { + return id; + } + + constexpr bool is_copy_constructed() const noexcept { + return copy_constructed; + } + + constexpr bool is_move_constructed() const noexcept { + return move_constructed; + } + + constexpr bool is_copy_assigned() const noexcept { + return copy_assigned; + } + + constexpr bool is_move_assigned() const noexcept { + return move_assigned; + } + + constexpr bool is_swapped() const noexcept { + return swapped; + } + + friend constexpr void swap(ActionTracker& left, ActionTracker& right) noexcept { + left.swapped = true; + right.swapped = true; + swap(left.id, right.id); + // leave the other members alone + } + +private: + int id; + bool copy_constructed = false; + bool move_constructed = false; + bool copy_assigned = false; + bool move_assigned = false; + bool swapped = false; +}; + +enum class RequireId { no, yes }; + +template +struct TrackingLayout { + template + requires constructible_from, Extents> + && (check_layout_mapping_policy_requirements()) + class mapping : public ActionTracker { + public: + using extents_type = Extents; + using index_type = extents_type::index_type; + using rank_type = extents_type::rank_type; + using layout_type = TrackingLayout; + using underlying_mapping = MpPolicy::template mapping; + + constexpr explicit mapping(int id) + requires default_initializable + : ActionTracker(id), mp() {} + + constexpr mapping(const extents_type& e, int id) : ActionTracker(id), mp(e) {} + + constexpr mapping(const extents_type& e) + requires (!to_underlying(ReqId)) + : ActionTracker(-1), constructed_with_extents_only{true}, mp(e) {} + + template + mapping(initializer_list) = delete; // we should never use list-initialization in + + constexpr mapping(const mapping& other) : ActionTracker(other), mp(other.mp) {} + + constexpr mapping(mapping&& other) noexcept : ActionTracker(move(other)), mp(move(other.mp)) {} + + // NB: special constructor for check_construction_from_other_mdspan's effects test + template + requires is_constructible_v + constexpr mapping(const mapping& other) : ActionTracker(other), mp(other.get_underlying()) {} + + constexpr mapping& operator=(const mapping&) = default; + constexpr mapping& operator=(mapping&&) = default; + + constexpr const extents_type& extents() const { + return mp.extents(); + } + + template + constexpr index_type operator()(IndexTypes... indices) const { + return mp(indices...); + } + + constexpr index_type required_span_size() const { + return mp.required_span_size(); + } + + constexpr bool is_unique() const { + return mp.is_unique(); + } + + constexpr bool is_exhaustive() const { + return mp.is_exhaustive(); + } + + constexpr bool is_strided() const { + return mp.is_strided(); + } + + constexpr index_type stride() const { + return mp.stride(); + } + + static constexpr bool is_always_unique() { + return underlying_mapping::is_always_unique(); + } + + static constexpr bool is_always_exhaustive() { + return underlying_mapping::is_always_exhaustive(); + } + + static constexpr bool is_always_strided() { + return underlying_mapping::is_always_strided(); + } + + constexpr bool operator==(const mapping& other) const { + return mp == other.mp; + } + + constexpr bool is_constructed_with_extents_only() const noexcept + requires (!to_underlying(ReqId)) + { + return constructed_with_extents_only; + } + + constexpr const underlying_mapping& get_underlying() const noexcept { + return mp; + } + + friend constexpr void swap(mapping& left, mapping& right) noexcept { + swap(static_cast(left), static_cast(right)); + swap(left.mp, right.mp); + } + + private: + bool constructed_with_extents_only = false; + underlying_mapping mp; + }; +}; + +static_assert(check_layout_mapping_policy_requirements, dextents>()); +static_assert(check_layout_mapping_policy_requirements, dextents>()); + +struct VectorBoolAccessor { + using offset_policy = VectorBoolAccessor; + using element_type = bool; + using reference = vector::reference; + using data_handle_type = vector::iterator; + + constexpr reference access(data_handle_type handle, size_t off) const { + return handle[static_cast(off)]; + } + + constexpr data_handle_type offset(data_handle_type handle, size_t off) const { + return handle + static_cast(off); + } +}; + +static_assert(check_accessor_policy_requirements()); + +template +class TrackingDataHandle : public ActionTracker { +public: + using data_handle_type = ElementType*; + + constexpr explicit TrackingDataHandle(int id, data_handle_type ptr_) noexcept : ActionTracker(id), ptr{ptr_} {} + + // NB: special constructor for check_construction_from_other_mdspan's effects test + template + requires is_convertible_v + constexpr TrackingDataHandle(const TrackingDataHandle& other) : ActionTracker(other) {} + + template + TrackingDataHandle(initializer_list) = delete; // we should never use list-initialization in + + constexpr TrackingDataHandle(const TrackingDataHandle& other) noexcept : ActionTracker(other), ptr{other.ptr} {} + + constexpr TrackingDataHandle(TrackingDataHandle&& other) noexcept + : ActionTracker(move(other)), ptr{exchange(other.ptr, nullptr)} {} + + constexpr TrackingDataHandle& operator=(const TrackingDataHandle&) noexcept = default; + constexpr TrackingDataHandle& operator=(TrackingDataHandle&&) noexcept = default; + + constexpr data_handle_type get_ptr() const noexcept { + return ptr; + } + + friend constexpr void swap(TrackingDataHandle& left, TrackingDataHandle& right) noexcept { + swap(static_cast(left), static_cast(right)); + swap(left.ptr, right.ptr); + } + +private: + data_handle_type ptr; +}; + +template +class AccessorWithTrackingDataHandle { +public: + using offset_policy = AccessorWithTrackingDataHandle; + using element_type = ElementType; + using reference = ElementType&; + using data_handle_type = TrackingDataHandle; + + constexpr reference access(data_handle_type handle, size_t off) const { + return handle.get_ptr()[off]; + } + + constexpr data_handle_type offset(data_handle_type handle, size_t off) const { + return TrackingDataHandle{handle.get_id(), handle.get_ptr() + off}; + } +}; + +template +class TrackingAccessor : public ActionTracker { +public: + using offset_policy = TrackingAccessor; + using element_type = ElementType; + using reference = ElementType&; + using data_handle_type = TrackingDataHandle; + + constexpr explicit TrackingAccessor(int id) noexcept : ActionTracker(id) {} + + // NB: special constructor for check_construction_from_other_mdspan's effects test + template + requires is_convertible_v + constexpr TrackingAccessor(const TrackingAccessor& other) : ActionTracker(other) {} + + constexpr reference access(data_handle_type handle, size_t off) const { + return handle.get_ptr()[off]; + } + + constexpr data_handle_type offset(data_handle_type handle, size_t off) const { + return data_handle_type{handle.get_id(), handle.get_ptr() + off}; + } + + friend constexpr void swap(TrackingAccessor& left, TrackingAccessor& right) noexcept { + swap(static_cast(left), static_cast(right)); + } +}; + +static_assert(check_accessor_policy_requirements>()); + +template +struct AccessorWithCustomOffsetPolicy { + using offset_policy = default_accessor; + using element_type = offset_policy::element_type; + using reference = offset_policy::reference; + using data_handle_type = offset_policy::data_handle_type; + + AccessorWithCustomOffsetPolicy() = default; + + // NB: special constructor for check_construction_from_other_mdspan's explicitness test + template + requires is_convertible_v + constexpr explicit AccessorWithCustomOffsetPolicy(AccessorWithCustomOffsetPolicy) noexcept {} + + constexpr operator const offset_policy&() const { + return offpol; + } + + constexpr reference access(data_handle_type handle, size_t off) const { + return offpol(handle, off); + } + + constexpr data_handle_type offset(data_handle_type handle, size_t off) const { + return offpol.offset(handle, off); + } + +private: + offset_policy offpol; +}; + +static_assert(check_accessor_policy_requirements>()); + +template +struct TrivialAccessor { + using offset_policy = TrivialAccessor; + using element_type = ElementType; + using reference = ElementType&; + using data_handle_type = ElementType*; + + constexpr reference access(data_handle_type handle, size_t off) const noexcept { + return handle[off]; + } + + constexpr data_handle_type offset(data_handle_type handle, size_t off) const noexcept { + return handle + off; + } + + int member; +}; + +static_assert(check_accessor_policy_requirements>()); +static_assert(is_trivial_v>); + +constexpr void check_modeled_concepts() { + using Mds = mdspan, TrackingLayout, + AccessorWithCustomOffsetPolicy>; + static_assert(copyable); + static_assert(is_nothrow_move_constructible_v); + static_assert(is_nothrow_move_assignable_v); + static_assert(is_nothrow_swappable_v); + static_assert( + is_trivially_copyable_v + == (is_trivially_copyable_v && is_trivially_copyable_v + && is_trivially_copyable_v) ); +} + +constexpr void check_member_types() { + using Ext = extents; + using Layout = layout_stride; + using Accessor = TrivialAccessor; + using Mds = mdspan; + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as>); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); +} + +constexpr void check_observers() { + using Ext = extents; + using Mds = mdspan>; + + { // Check results + static_assert(Mds::rank() == Ext::rank()); + static_assert(Mds::rank_dynamic() == Ext::rank_dynamic()); + static_assert(Mds::static_extent(0) == Ext::static_extent(0)); + static_assert(Mds::static_extent(1) == Ext::static_extent(1)); + static_assert(Mds::static_extent(2) == Ext::static_extent(2)); + static_assert(Mds::static_extent(3) == Ext::static_extent(3)); + static_assert(Mds::static_extent(4) == Ext::static_extent(4)); + } + + { // Check return types + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + } + + { // Check noexceptness + static_assert(noexcept(Mds::rank())); + static_assert(noexcept(Mds::rank_dynamic())); + static_assert(noexcept(Mds::static_extent(0))); + } +} + +constexpr void check_default_constructor() { + { // Check constraint: 'rank_dynamic() > 0' + static_assert(is_default_constructible_v>>); + static_assert(!is_default_constructible_v>>); + static_assert(!is_default_constructible_v>>); + } + + { // Check constraints: 'is_default_constructible_v' + static_assert(is_default_constructible_v, layout_right, VectorBoolAccessor>>); + static_assert( + !is_default_constructible_v, layout_right, TrackingAccessor>>); + } + + { // Check constraint: 'is_default_constructible_v' + static_assert(!is_default_constructible_v, layout_stride>>); + static_assert(!is_default_constructible_v, TrackingLayout<>>>); + } + + { // Check constraint: 'is_default_constructible_v' + static_assert(is_default_constructible_v< + mdspan, layout_right, AccessorWithCustomOffsetPolicy>>); + static_assert( + !is_default_constructible_v, layout_right, TrackingAccessor>>); + } + + { // Check effects + mdspan, layout_stride, TrivialAccessor> mds{}; + assert(mds.data_handle() == nullptr); + assert((mds.mapping().strides() == array{0, 0, 1})); + assert(mds.accessor().member == 0); + } +} + +constexpr void check_defaulted_copy_and_move_constructors() { + using Ext = extents; + using Mds = mdspan, TrackingAccessor>; + short bits_of_218[] = {1, 1, 0, 1, 1, 0, 1, 0}; + + { // Check defaulted copy constructor + Mds mds1{ + TrackingDataHandle{2, bits_of_218}, TrackingLayout<>::mapping(4), TrackingAccessor{8}}; + Mds mds2{mds1}; + assert(mds2.data_handle().is_copy_constructed()); + assert(mds2.mapping().is_copy_constructed()); + assert(mds2.accessor().is_copy_constructed()); + } + + { // Check defaulted move constructor + Mds mds1{ + TrackingDataHandle{2, bits_of_218}, TrackingLayout<>::mapping(4), TrackingAccessor{8}}; + Mds mds2{move(mds1)}; + assert(mds2.data_handle().is_move_constructed()); + assert(mds2.mapping().is_move_constructed()); + assert(mds2.accessor().is_move_constructed()); + } +} + +constexpr void check_data_handle_and_indices_pack_constructor() { + { // Check constraint: '(is_convertible_v && ...)' + using Mds = mdspan>; + static_assert(is_constructible_v); + static_assert(is_constructible_v); + static_assert(is_constructible_v); + static_assert(is_constructible_v>); + static_assert(!is_constructible_v); + } + + { // Check constraint: '(is_nothrow_constructible && ...)' + using Mds = mdspan>; + static_assert(is_constructible_v>); + static_assert(!is_constructible_v>); + } + + { // Check constraint: 'N == rank() || N == rank_dynamic()' + using Mds = mdspan>; + static_assert(!is_constructible_v); + static_assert(is_constructible_v); + static_assert(!is_constructible_v); + static_assert(is_constructible_v); + } + + { // Check constraint: 'is_constructible_v' + static_assert(is_constructible_v, layout_left>, int* const, int, int>); + static_assert(!is_constructible_v, layout_stride>, int*, int, int>); + static_assert(!is_constructible_v, TrackingLayout<>>, int*, int, int>); + } + + { // Check constraint: 'is_default_constructible_v' + static_assert(is_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, int, int>); + static_assert( + !is_constructible_v, layout_right, TrackingAccessor>, int*, int, int>); + } + + { // Check explicitness + using Mds = mdspan>; + static_assert(NotImplicitlyConstructibleFrom); + static_assert(NotImplicitlyConstructibleFrom); + static_assert(!NotImplicitlyConstructibleFrom); + } + + { // Check effects: 'direct-non-list-initializes ptr_ with std::move(p)' + int ints[4] = {1, 2, 3, 4}; + mdspan, layout_right, AccessorWithTrackingDataHandle> mds{ + TrackingDataHandle{1, ints}, 2, 2}; + assert(mds.data_handle().is_move_constructed()); + } + + { // Check effects: 'direct-non-list-initializes map_ with + // extents_type(static_cast(std::move(exts))...)' + using Ext = dextents; + struct FunnyIndex { + constexpr operator Ext::index_type() const& noexcept { + return 1; + } + + constexpr operator integral auto() && noexcept { + return 1; + } + + constexpr operator Ext::index_type() && noexcept { + return 3; + } + }; + + char digits[9] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'}; + FunnyIndex i; + mdspan mds{digits, i, i}; + assert(mds.extent(0) == 3); + assert(mds.extent(1) == 3); + } + + { // Check effects: 'value-initializes acc_' + int ints[4] = {2, 4, 8, 16}; + mdspan, layout_left, TrivialAccessor> mds{ints, 2, 2}; + assert(mds.accessor().member == 0); + } +} + +constexpr void check_data_handle_and_span_array_constructors() { + { // Check constraint: 'is_convertible_v' + using Mds = mdspan>; + static_assert(is_constructible_v>); + static_assert(is_constructible_v>); + static_assert(is_constructible_v, 3>>); + static_assert(is_constructible_v, 3>>); + static_assert(!is_constructible_v>); + static_assert(!is_constructible_v>); + } + + { // Check constraint: 'is_nothrow_constructible' + using Mds = mdspan>; + static_assert(is_constructible_v, 2>>); + static_assert(is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + } + + { // Check constraint: 'N == rank() || N == rank_dynamic()' + using Mds = mdspan>; + static_assert(!is_constructible_v>); + static_assert(!is_constructible_v>); + static_assert(is_constructible_v>); + static_assert(is_constructible_v>); + static_assert(!is_constructible_v>); + static_assert(!is_constructible_v>); + static_assert(is_constructible_v>); + static_assert(is_constructible_v>); + } + + { // Check constraint: 'is_constructible_v' + static_assert(is_constructible_v, layout_left>, int* const, span>); + static_assert(is_constructible_v, layout_left>, int* const, array>); + static_assert( + is_constructible_v, TrackingLayout>, + int* const, span>); + static_assert( + is_constructible_v, TrackingLayout>, + int* const, array>); + static_assert(!is_constructible_v, layout_stride>, int*, span>); + static_assert(!is_constructible_v, layout_stride>, int*, array>); + static_assert(!is_constructible_v, TrackingLayout<>>, int*, span>); + static_assert(!is_constructible_v, TrackingLayout<>>, int*, array>); + } + + { // Check constraint: 'is_default_constructible_v' + static_assert(is_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, span>); + static_assert(is_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, array>); + static_assert(!is_constructible_v, layout_right, TrackingAccessor>, int*, + span>); + static_assert(!is_constructible_v, layout_right, TrackingAccessor>, int*, + array>); + } + + { // Check explicitness + using Mds = mdspan>; + static_assert(NotImplicitlyConstructibleFrom>); + static_assert(NotImplicitlyConstructibleFrom>); + static_assert(!NotImplicitlyConstructibleFrom>); + static_assert(!NotImplicitlyConstructibleFrom>); + static_assert(NotImplicitlyConstructibleFrom, 3>>); + static_assert(NotImplicitlyConstructibleFrom, 3>>); + static_assert(!NotImplicitlyConstructibleFrom, 1>>); + static_assert(!NotImplicitlyConstructibleFrom, 1>>); + } + + { // Check effects: 'direct-non-list-initializes ptr_ with std::move(p)' + int ints[4] = {1, 2, 3, 4}; + array indices{2, 2}; + mdspan, layout_right, AccessorWithTrackingDataHandle> mds1{ + TrackingDataHandle{1, ints}, indices}; + assert(mds1.data_handle().is_move_constructed()); + span s{indices}; + mdspan, layout_right, AccessorWithTrackingDataHandle> mds2{ + TrackingDataHandle{1, ints}, s}; + assert(mds2.data_handle().is_move_constructed()); + } + + { // Check effects: 'direct-non-list-initializes map_ with extents_type(exts)' + using Ext = dextents; + struct FunnyIndex { + constexpr operator integral auto() & noexcept { + return 1; + } + + constexpr operator integral auto() const& noexcept { + return 1; + } + + constexpr operator Ext::index_type() const& noexcept { + return 3; + } + + constexpr operator integral auto() && noexcept { + return 1; + } + }; + + char digits[9] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'}; + array indices; + mdspan> mds1{digits, indices}; + assert(mds1.extent(0) == 3); + span s{indices}; + mdspan mds2{digits, s}; + assert(mds2.extent(0) == 3); + assert(mds2.extent(1) == 3); + } + + { // Check effects: 'value-initializes acc_' + int ints[4] = {1, 3, 7, 15}; + array indices{2, 2}; + mdspan, layout_left, TrivialAccessor> mds1{ints, indices}; + assert(mds1.accessor().member == 0); + span s{indices}; + mdspan, layout_left, TrivialAccessor> mds2{ints, s}; + assert(mds2.accessor().member == 0); + } +} + +constexpr void check_data_handle_and_extents_constructor() { + { // Check constraint: 'is_constructible_v' + static_assert(is_constructible_v>, int*, dextents>); + static_assert(is_constructible_v>, int*, dextents>); + static_assert(!is_constructible_v, layout_stride>, int*, dextents>); + static_assert(!is_constructible_v>, int*, dextents>); + static_assert(!is_constructible_v>, int*, extents>); + } + + { // Check constraint: is_default_constructible_v + static_assert(is_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, dextents>); + static_assert(is_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, extents>); + static_assert(!is_constructible_v, layout_right, TrackingAccessor>, int*, + dextents>); + static_assert(!is_constructible_v, layout_right, TrackingAccessor>, int*, + extents>); + } + + { // Check effects: 'direct-non-list-initializes ptr_ with std::move(p)' + char physics[4] = {'s', 't', 'v', 'a'}; + mdspan, layout_right, AccessorWithTrackingDataHandle> mds{ + TrackingDataHandle{1, physics}, extents{}}; + assert(mds.data_handle().is_move_constructed()); + } + + { // Check effects: "direct-non-list-initializes map_ with ext" + short lucky_numbers[6] = {2, 15, 17, 31, 34, 35}; + mdspan, TrackingLayout> mds{ + lucky_numbers, extents{}}; + assert(mds.mapping().is_constructed_with_extents_only()); + } + + { // Check effects: 'value-initializes acc_' + int ints[4] = {1, 22, 333, 4444}; + mdspan, layout_left, TrivialAccessor> mds{ints, extents{}}; + assert(mds.accessor().member == 0); + } +} + +constexpr void check_data_handle_and_mapping_constructor() { + { // Check constraint: 'is_default_constructible_v' + static_assert(is_constructible_v, layout_left, VectorBoolAccessor>, + vector::iterator, layout_left::mapping>>); + static_assert(is_constructible_v, layout_left>, int* const, + layout_left::mapping>>); + static_assert(!is_constructible_v< + mdspan, extents, TrackingLayout<>, TrackingAccessor>>, + vector*, TrackingLayout<>::mapping>>); + static_assert(!is_constructible_v< + mdspan, extents, TrackingLayout<>, TrackingAccessor>>, + deque* const, TrackingLayout<>::mapping>>); + } + + { // Check effect: 'direct-non-list-initializes ptr_ with std::move(p)' + using Ext = extents; + char banana[6] = {'b', 'a', 'n', 'a', 'n', 'a'}; + mdspan> mds{ + TrackingDataHandle{1, banana}, layout_stride::mapping{Ext{}, array{3, 6, 1}}}; + assert(mds.data_handle().is_move_constructed()); + } + + { // Check effect: 'direct-non-list-initializes map_ with m' + using Ext = extents; + char x[9] = {'\\', ' ', '/', ' ', 'X', ' ', '/', ' ', '\\'}; + mdspan> mds{x, TrackingLayout<>::mapping{Ext{}, 23}}; + assert(mds.mapping().is_copy_constructed()); + } + + { // Check effect: 'value-initializes acc_' + using Ext = extents; + int twelve[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + mdspan> mds{ + twelve, layout_stride::mapping{Ext{}, array{2, 1, 4}}}; + assert(mds.accessor().member == 0); + } +} + +constexpr void check_data_handle_and_mapping_and_accessor_constructor() { + using Ext = extents; + using Mds = mdspan, TrackingAccessor>; + + unsigned int identity_matrix[] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; + Mds mds{TrackingDataHandle{16, identity_matrix}, TrackingLayout<>::mapping(17), + TrackingAccessor{18}}; + + // Check effects: + // - Direct-non-list-initializes ptr_ with std::move(p), + // - direct-non-list-initializes map_ with m, and + // - direct-non-list-initializes acc_ with a. + assert(mds.data_handle().is_move_constructed()); + assert(mds.data_handle().get_id() == 16); + assert(mds.mapping().is_copy_constructed()); + assert(mds.mapping().get_id() == 17); + assert(mds.accessor().is_copy_constructed()); + assert(mds.accessor().get_id() == 18); +} + +constexpr void check_construction_from_other_mdspan() { + { // Check constraint: 'is_constructible_v&>' + static_assert(is_constructible_v, layout_stride>, + mdspan, layout_right>>); + static_assert(!is_constructible_v, layout_left>, + mdspan, layout_right>>); + static_assert(!is_constructible_v, layout_left>, + mdspan, layout_left>>); + } + + { // Check constraint: 'is_constructible_v' + using Ext = extents; + static_assert(is_constructible_v>, + mdspan>>); + static_assert(!is_constructible_v>, + mdspan>>); + } + + { // Check explicitness + static_assert(NotImplicitlyConstructibleFrom>, mdspan>>); + static_assert(NotImplicitlyConstructibleFrom, layout_left>, + mdspan, layout_stride>>); + static_assert(!NotImplicitlyConstructibleFrom, layout_stride>, + mdspan, layout_left>>); + static_assert(NotImplicitlyConstructibleFrom< + mdspan, layout_left, AccessorWithCustomOffsetPolicy>, + mdspan, layout_left, AccessorWithCustomOffsetPolicy>>); + static_assert(!NotImplicitlyConstructibleFrom< + mdspan, layout_left, default_accessor>, + mdspan, layout_left, default_accessor>>); + } + + { // Check effects + int data[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + + using Ext = dextents; + mdspan, TrackingAccessor> mds1{TrackingDataHandle{4, data}, + TrackingLayout<>::mapping{Ext{3, 3, 3}, 4}, TrackingAccessor{4}}; + mdspan, TrackingLayout<>, TrackingAccessor> mds2{mds1}; + assert(mds2.data_handle().is_copy_constructed()); + assert(mds2.mapping().is_copy_constructed()); + assert(mds2.accessor().is_copy_constructed()); + } +} + +constexpr void check_defaulted_copy_and_move_assignment_operators() { + using Ext = extents; + using Mds = mdspan, TrackingAccessor>; + short bits_of_218[] = {1, 1, 0, 1, 1, 0, 1, 0}; + short bits_of_248[] = {1, 1, 1, 1, 1, 0, 0, 0}; + + { // Check defaulted copy assignment operator + Mds mds1{ + TrackingDataHandle{2, bits_of_218}, TrackingLayout<>::mapping(4), TrackingAccessor{8}}; + Mds mds2{ + TrackingDataHandle{3, bits_of_248}, TrackingLayout<>::mapping(5), TrackingAccessor{9}}; + mds1 = mds2; + assert(mds1.data_handle().is_copy_assigned()); + assert(mds1.mapping().is_copy_assigned()); + assert(mds1.accessor().is_copy_assigned()); + } + + { // Check defaulted move assignment operator + Mds mds1{ + TrackingDataHandle{2, bits_of_218}, TrackingLayout<>::mapping(4), TrackingAccessor{8}}; + Mds mds2{ + TrackingDataHandle{3, bits_of_248}, TrackingLayout<>::mapping(5), TrackingAccessor{9}}; + mds1 = move(mds2); + assert(mds1.data_handle().is_move_assigned()); + assert(mds1.mapping().is_move_assigned()); + assert(mds1.accessor().is_move_assigned()); + } +} + +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 +template +concept CanCallMultidimSubscriptOp = requires(const Mds& mds, IndexTypes... indices) { + { mds[indices...] } -> same_as; +}; + +constexpr void check_multidimensional_subscript_operator() { + { // Check constraint: '(is_convertible_v && ...)' + using Mds = mdspan>; + static_assert(CanCallMultidimSubscriptOp); + static_assert(CanCallMultidimSubscriptOp); + static_assert(CanCallMultidimSubscriptOp); + static_assert(CanCallMultidimSubscriptOp); + static_assert(CanCallMultidimSubscriptOp, int, int, int, int>); +#ifndef __clang__ // TRANSITION, Clang 17 + static_assert(!CanCallSubscriptOperator); +#endif // __clang__ + } + + { // Check constraint: '(is_nothrow_constructible_v && ...)' + using Mds = mdspan>; + static_assert(CanCallMultidimSubscriptOp, int>); +#ifndef __clang__ // TRANSITION, Clang 17 + static_assert(!CanCallSubscriptOperator, int>); +#endif // __clang__ + } + + { // Check constraint: 'sizeof...(OtherIndexTypes) == rank()' + using Mds = mdspan>; + static_assert(CanCallMultidimSubscriptOp); +#ifndef __clang__ // TRANSITION, Clang 17 + static_assert(!CanCallSubscriptOperator); + static_assert(!CanCallSubscriptOperator); +#endif // __clang__ + } + + { // Check correctness + using Ext = extents; + vector bools{true, false, false, true}; + mdspan, VectorBoolAccessor> mds{ + bools.begin(), TrackingLayout::mapping(5)}; + same_as::reference> decltype(auto) r1 = mds[1, 1]; + assert(r1); + same_as::reference> decltype(auto) r2 = as_const(mds)[0, 1]; + assert(!r2); + } + + { // Check that indices are moved and then casted to 'index_type' + using Ext = dextents; + struct FunnyIndex { + constexpr operator integral auto() const& noexcept { + return 0; + } + + constexpr operator integral auto() && noexcept { + return 0; + } + + constexpr operator Ext::index_type() && noexcept { + return 1; + } + }; + + int mat2x2[4] = {0, 0, 0, 1}; + mdspan mds{mat2x2, 2, 2}; + FunnyIndex i; + assert((mds[i, i] == 1)); + } + + { // Check that indices are passed by value + struct WeirdIndex { + WeirdIndex() = default; + constexpr WeirdIndex(const WeirdIndex&) : val{1} {} + constexpr WeirdIndex(WeirdIndex&&) : val{2} {} + + constexpr operator int() const noexcept { + return val; + } + + int val = 0; + }; + + int ten2x2x3[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + mdspan> mds{ten2x2x3}; + WeirdIndex i; + assert((mds[i, i, move(i)] == 1)); + } +} +#endif // __cpp_multidimensional_subscript + +template +concept CanCallSubscriptOp = requires(const Mds& mds, span s, const array& a) { + { mds[s] } -> same_as; + { mds[a] } -> same_as; +}; + +constexpr void check_span_array_subscript_operator() { + { // Check constraint: 'is_convertible_v + using Mds = mdspan>; + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp); + static_assert(CanCallSubscriptOp>); + static_assert(!CanCallSubscriptOp); + } + + { // Check constraint: 'is_nothrow_constructible_v' + using Mds = mdspan>; + static_assert(CanCallSubscriptOp>); + static_assert(!CanCallSubscriptOp>); + } + + { // Check function argument: '(span|array)::size() == rank()' + using Mds = mdspan>; + static_assert(!CanCallSubscriptOp); + static_assert(!CanCallSubscriptOp); + } + + { // Check correctness + using Ext = extents; + vector bools{true, true, true, true, false, true, true, true, true}; + mdspan, VectorBoolAccessor> mds{bools.begin(), TrackingLayout<>::mapping(99)}; + + array a1{1, 1}; + same_as::reference> decltype(auto) ar1 = mds[a1]; + assert(!ar1); + span s1{a1}; + same_as::reference> decltype(auto) sr1 = mds[s1]; + assert(!sr1); + + array a2{2, 2}; + same_as::reference> decltype(auto) ar2 = as_const(mds)[a2]; + assert(ar2); + span s2{a2}; + same_as::reference> decltype(auto) sr2 = as_const(mds)[s2]; + assert(sr2); + } + + { // Check that indices are expanded in as_const function and passed to multidimensional subscript operator + using Ext = dextents; + struct FunkyIndex { + FunkyIndex() = default; + FunkyIndex(FunkyIndex&) = delete; + FunkyIndex(const FunkyIndex&) = default; + + constexpr operator integral auto() const& noexcept { + return 0; + } + + constexpr operator integral auto() && noexcept { + return 0; + } + + constexpr operator Ext::index_type() && noexcept { + return 1; + } + }; + + char alpha[4] = {'a', 'b', 'c', 'd'}; + mdspan mds{alpha, 2, 2}; + array a; + assert(mds[a] == 'd'); + span s{a}; + assert(mds[s] == 'd'); + } +} + +constexpr void check_size() { + const int some_data[60] = {}; + + { // Not empty mdspan + mdspan mds1{some_data, extents{}}; + assert(mds1.size() == 60); + mdspan mds2{some_data, extents{12}}; + assert(mds2.size() == 60); + mdspan mds3{some_data, dextents{2, 3, 5}}; + assert(mds3.size() == 30); + } + + { // Empty mdspan + mdspan mds1{some_data, extents{}}; + assert(mds1.size() == 0); + mdspan mds2{some_data, extents{0}}; + assert(mds2.size() == 0); + mdspan mds3{some_data, extents{0}}; + assert(mds3.size() == 0); + } + + { // Mdspan with 'rank() == 0' + mdspan mds{some_data, extents{}}; + assert(mds.size() == 1); + } + + { // Mdspan whose index space size would not be representable as index_type if 0 wasn't there + mdspan mds1{some_data, extents{}}; + assert(mds1.size() == 0); + mdspan mds2{some_data, dextents{32767, 3, 0}}; + assert(mds2.size() == 0); + } + + { // Other properties + mdspan mds{some_data, extents{}}; + same_as decltype(auto) s1 = mds.size(); + assert(s1 == 60); + static_assert(noexcept(mds.size())); + same_as decltype(auto) s2 = as_const(mds).size(); + assert(s2 == 60); + static_assert(noexcept(as_const(mds).size())); + } +} + +constexpr void check_empty() { + const int some_data[24] = {}; + + { // Not empty mdspan + mdspan mds1{some_data, extents{}}; + assert(!mds1.empty()); + mdspan mds2{some_data, extents{3}}; + assert(!mds2.empty()); + mdspan mds3{some_data, dextents{3, 3, 4}}; + assert(!mds3.empty()); + } + + { // Empty mdspan + mdspan mds1{some_data, extents{}}; + assert(mds1.empty()); + mdspan mds2{some_data, extents{0}}; + assert(mds2.empty()); + mdspan mds3{some_data, extents{0}}; + assert(mds3.empty()); + } + + { // Mdspan with 'rank() == 0' + mdspan mds{some_data, extents{}}; + assert(!mds.empty()); + } + + { // Mdspan whose index space size would not be representable as index_type if 0 wasn't there + mdspan mds1{some_data, extents{}}; + assert(mds1.empty()); + mdspan mds2{some_data, dextents{32767, 3, 0}}; + assert(mds2.empty()); + } + + { // Other properties + mdspan mds{some_data, extents{}}; + same_as decltype(auto) b1 = mds.empty(); + assert(b1); + static_assert(noexcept(mds.empty())); + same_as decltype(auto) b2 = as_const(mds).empty(); + assert(b2); + static_assert(noexcept(as_const(mds).empty())); + } +} + +constexpr void check_swap() { + using E = extents; + using Mds = mdspan, TrackingAccessor>; + static_assert(is_nothrow_swappable_v); + static_assert(!is_swappable_v); + + int a1[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + Mds mds1{TrackingDataHandle{1, a1}, TrackingLayout<>::mapping(1), TrackingAccessor{1}}; + int a2[9] = {3, 0, 0, 0, 3, 0, 0, 0, 3}; + Mds mds2{TrackingDataHandle{3, a2}, TrackingLayout<>::mapping(3), TrackingAccessor{3}}; + swap(mds1, mds2); + static_assert(is_void_v); + + assert(mds1.data_handle().get_id() == 3); + assert(mds1.data_handle().is_swapped()); + assert(mds1.mapping().get_id() == 3); + assert(mds1.mapping().is_swapped()); + assert(mds1.accessor().get_id() == 3); + assert(mds1.accessor().is_swapped()); + assert((mds1[array{1, 1}] == 3)); + assert((mds1[array{0, 1}] == 0)); + + assert(mds2.data_handle().get_id() == 1); + assert(mds2.data_handle().is_swapped()); + assert(mds2.mapping().get_id() == 1); + assert(mds2.mapping().is_swapped()); + assert(mds2.accessor().get_id() == 1); + assert(mds2.accessor().is_swapped()); + assert((mds2[array{1, 1}] == 1)); + assert((mds2[array{0, 1}] == 0)); +} + +constexpr void check_getters() { + int data[6] = {1, 2, 3, 4, 5, 6}; + auto mds = mdspan(data, 2, 3); + + { // Check 'extents()' + same_as&> decltype(auto) e = mds.extents(); + assert((e == dextents{2, 3})); + assert(&e == &mds.mapping().extents()); + static_assert(noexcept(mds.extents())); + same_as&> decltype(auto) ce = as_const(mds).extents(); + assert(&ce == &e); + static_assert(noexcept(as_const(mds).extents())); + } + + { // Check 'data_handle()' + same_as decltype(auto) dh = mds.data_handle(); + assert(dh == data); + static_assert(noexcept(mds.data_handle())); + same_as decltype(auto) cdh = as_const(mds).data_handle(); + assert(&cdh == &dh); + static_assert(noexcept(as_const(mds).data_handle())); + } + + { // Check 'mapping()' + using E = dextents; + + same_as&> decltype(auto) mp = mds.mapping(); + assert((mp == layout_stride::mapping{E{2, 3}, array{3, 1}})); + static_assert(noexcept(mds.mapping())); + same_as&> decltype(auto) cmp = as_const(mds).mapping(); + assert(&cmp == &mp); + static_assert(noexcept(as_const(mds).mapping())); + } + + { // Check 'accessor()' + same_as&> decltype(auto) acc = mds.accessor(); + static_assert(noexcept(mds.accessor())); + same_as&> decltype(auto) cacc = as_const(mds).accessor(); + assert(&cacc == &acc); + static_assert(noexcept(as_const(mds).accessor())); + } +} + +constexpr void check_is_always_functions() { + using Mds = mdspan, layout_stride, TrivialAccessor>; + + { // Check results + static_assert(Mds::is_always_unique() == Mds::mapping_type::is_always_unique()); + static_assert(Mds::is_always_exhaustive() == Mds::mapping_type::is_always_exhaustive()); + static_assert(Mds::is_always_strided() == Mds::mapping_type::is_always_strided()); + } + + { // Check types + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + } + + { // Check noexceptness (strengthened) + static_assert(noexcept(Mds::is_always_unique()) == noexcept(Mds::mapping_type::is_always_unique())); + static_assert(noexcept(Mds::is_always_exhaustive()) == noexcept(Mds::mapping_type::is_always_exhaustive())); + static_assert(noexcept(Mds::is_always_strided()) == noexcept(Mds::mapping_type::is_always_strided())); + } +} + +constexpr void check_is_functions() { + using E = extents; + vector bools = {true, false, true, true, true, true, true, false, true, false, false, false}; + mdspan mds{ + bools.begin(), layout_stride::mapping{E{}, array{6, 1, 2}}}; + + { // Check results + assert(mds.is_unique() == mds.mapping().is_unique()); + assert(mds.is_exhaustive() == mds.mapping().is_exhaustive()); + assert(mds.is_strided() == mds.mapping().is_strided()); + } + + { // Check types + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + } + + { // Check noexceptness (strengthened) + static_assert(noexcept(mds.is_unique()) == noexcept(mds.mapping().is_unique())); + static_assert(noexcept(mds.is_exhaustive()) == noexcept(mds.mapping().is_exhaustive())); + static_assert(noexcept(mds.is_strided()) == noexcept(mds.mapping().is_strided())); + } +} + +constexpr void check_stride_function() { + using E = extents; + using Mds = mdspan; + + int data[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}; + Mds mds{data, layout_stride::mapping{E{}, array{15, 1, 3}}}; + same_as decltype(auto) s1 = mds.stride(0); + assert(s1 == 15); + same_as decltype(auto) s2 = as_const(mds).stride(1); + assert(s2 == 1); + + struct ConvertibleToRankType { + constexpr operator integral auto() const { + return 1; + } + + constexpr operator Mds::rank_type() const { + return 2; + } + }; + + same_as decltype(auto) s3 = as_const(mds).stride(ConvertibleToRankType{}); + assert(s3 == 3); +} + +constexpr void check_deduction_guides() { + { // CArray + int carray[10]{}; + mdspan mds{carray}; + static_assert(same_as>>); + } + + { // Pointer&& + float carray[20]{}; + float* ptr = carray; + mdspan mds{ptr}; + static_assert(same_as>>); + } + + { // ElementType*, Integrals... + byte carray[30]{}; + byte* ptr = carray; + mdspan mds1{ptr, 6, 5}; + static_assert(same_as>>); + mdspan mds2{ptr, 2, 3, 5}; + static_assert(same_as>>); + } + + { // ElementType*, span + const int carray[40]{}; + const int* ptr = carray; + const int exts1[] = {2, 4, 5}; + span s1{exts1}; + mdspan mds1{ptr, s1}; + static_assert(same_as>>); + const long exts2[] = {2, 2, 5, 2}; + span s2{exts2}; + mdspan mds2{ptr, s2}; + static_assert(same_as>>); + } + + { // ElementType*, const array& + const char carray[50]{}; + const char* ptr = carray; + array a1{5, 10}; + mdspan mds1{ptr, a1}; + static_assert(same_as>>); + array a2{2, 5, 5}; + mdspan mds2{ptr, a2}; + static_assert(same_as>>); + } + + { // ElementType*, const extents& + const double carray[60]{}; + const double* ptr = carray; + extents exts1; + mdspan mds1{ptr, exts1}; + static_assert(same_as>>); + extents exts2{4}; + mdspan mds2{ptr, exts2}; + static_assert(same_as>>); + } + + { // ElementType*, const MappingType& + const long carray[70]{}; + const long* ptr = carray; + mdspan mds1{ptr, layout_left::mapping>{}}; + static_assert(same_as, layout_left>>); + mdspan mds2{ptr, layout_stride::mapping>{dextents{2, 3, 5}, array{3, 1, 6}}}; + static_assert(same_as, layout_stride>>); + } + + { // const typename AccessorType::data_handle_type&, const MappingType&, const AccessorType& + vector bools = {true, false, true, false}; + mdspan mds{bools.begin(), TrackingLayout<>::mapping>(1), VectorBoolAccessor{}}; + static_assert(same_as, TrackingLayout<>, VectorBoolAccessor>>); + } +} + +constexpr bool test() { + check_modeled_concepts(); + check_member_types(); + check_observers(); + check_default_constructor(); + check_defaulted_copy_and_move_constructors(); + check_data_handle_and_indices_pack_constructor(); + check_data_handle_and_span_array_constructors(); + check_data_handle_and_extents_constructor(); + check_data_handle_and_mapping_constructor(); + check_data_handle_and_mapping_and_accessor_constructor(); + check_construction_from_other_mdspan(); + check_defaulted_copy_and_move_assignment_operators(); +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 + check_multidimensional_subscript_operator(); +#endif // __cpp_multidimensional_subscript + check_span_array_subscript_operator(); + check_size(); + check_empty(); + check_swap(); + check_getters(); + check_is_always_functions(); + check_is_functions(); + check_stride_function(); + check_deduction_guides(); + return true; +} + +int main() { + static_assert(test()); + test(); +}