diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index b559f9c8336..4483dfeb8e1 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -177,6 +177,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/list ${CMAKE_CURRENT_LIST_DIR}/inc/locale ${CMAKE_CURRENT_LIST_DIR}/inc/map + ${CMAKE_CURRENT_LIST_DIR}/inc/mdspan ${CMAKE_CURRENT_LIST_DIR}/inc/memory ${CMAKE_CURRENT_LIST_DIR}/inc/memory_resource ${CMAKE_CURRENT_LIST_DIR}/inc/mutex diff --git a/stl/inc/__msvc_all_public_headers.hpp b/stl/inc/__msvc_all_public_headers.hpp index ccb3267943a..e6ecca51ab5 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -107,6 +107,7 @@ #include #include #include +#include #include #include #include diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json index 0966ec2accf..0243c0b08f3 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -83,6 +83,7 @@ "list", "locale", "map", + "mdspan", "memory", "memory_resource", "mutex", diff --git a/stl/inc/mdspan b/stl/inc/mdspan new file mode 100644 index 00000000000..ed037824dcf --- /dev/null +++ b/stl/inc/mdspan @@ -0,0 +1,1460 @@ +// mdspan standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef _MDSPAN_ +#define _MDSPAN_ +#include +#if _STL_COMPILER_PREPROCESSOR +#if !_HAS_CXX23 || !defined(__cpp_lib_concepts) // TRANSITION, GH-395 +_EMIT_STL_WARNING(STL4038, "The contents of are available only with C++23 or later."); +#else // ^^^ not supported / supported language mode vvv +#include +#include +#include +#include +#include + +#pragma pack(push, _CRT_PACKING) +#pragma warning(push, _STL_WARNING_LEVEL) +#pragma warning(disable : _STL_DISABLED_WARNINGS) +_STL_DISABLE_CLANG_WARNINGS +#pragma push_macro("new") +#undef new + +// TRANSITION, non-_Ugly attribute tokens +#pragma push_macro("empty_bases") +#undef empty_bases + +_STD_BEGIN +template +struct _Maybe_empty_array { + array<_IndexType, _Size> _Array{}; +}; + +template +struct _Maybe_empty_array<_IndexType, 0> {}; + +template +inline constexpr size_t _Calculate_rank_dynamic = (static_cast(_Extents == dynamic_extent) + ... + 0); + +struct _Extents_from_tuple { + explicit _Extents_from_tuple() = default; +}; + +_EXPORT_STD template +class extents : private _Maybe_empty_array<_IndexType, _Calculate_rank_dynamic<_Extents...>> { +public: + using index_type = _IndexType; + using size_type = make_unsigned_t; + using rank_type = size_t; + + static_assert(_Is_standard_integer, + "IndexType must be a signed or unsigned integer type (N4950 [mdspan.extents.overview]/1.1)."); + static_assert(((_Extents == dynamic_extent || _STD in_range(_Extents)) && ...), + "Each element of Extents must either be equal to dynamic_extent, or be representable as a value of type " + "IndexType (N4950 [mdspan.extents.overview]/1.2)."); + + static constexpr rank_type _Rank = sizeof...(_Extents); + static constexpr rank_type _Rank_dynamic = _Calculate_rank_dynamic<_Extents...>; + static constexpr array _Static_extents = {_Extents...}; + static constexpr bool _Multidim_index_space_size_is_always_zero = ((_Extents == 0) || ...); + +private: + _NODISCARD static consteval auto _Make_dynamic_indices() noexcept { + array _Result{}; + rank_type _Counter = 0; + for (rank_type _Idx = 0; _Idx < _Rank; ++_Idx) { + _Result[_Idx] = _Counter; + if (_Static_extents[_Idx] == dynamic_extent) { + ++_Counter; + } + } + _Result[_Rank] = _Counter; + return _Result; + } + + static constexpr array _Dynamic_indices = _Make_dynamic_indices(); + + _NODISCARD static consteval auto _Make_dynamic_indices_inv() noexcept { + array _Result{}; + rank_type _Counter = 0; + for (rank_type _Idx = 0; _Idx < _Rank; ++_Idx) { + if (_Static_extents[_Idx] == dynamic_extent) { + _Analysis_assume_(_Counter < _Rank_dynamic); // TRANSITION, DevCom-923103 + _Result[_Counter] = _Idx; + ++_Counter; + } + } + return _Result; + } + + static constexpr array _Dynamic_indices_inv = _Make_dynamic_indices_inv(); + + using _Base = _Maybe_empty_array<_IndexType, _Rank_dynamic>; + + template + constexpr explicit extents( + const extents<_OtherIndexType, _OtherExtents...>& _Other, index_sequence<_Indices...>) noexcept + : _Base{static_cast(_Other.extent(_Dynamic_indices_inv[_Indices]))...} { + _STL_INTERNAL_STATIC_ASSERT(sizeof...(_OtherExtents) == _Rank); + _STL_INTERNAL_STATIC_ASSERT( + ((_OtherExtents == dynamic_extent || _Extents == dynamic_extent || _OtherExtents == _Extents) && ...)); +#if _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (rank() > 0) { + for (rank_type _Idx = 0; _Idx < _Rank; ++_Idx) { + if constexpr (rank() != rank_dynamic()) { + _STL_VERIFY(_Static_extents[_Idx] == dynamic_extent + || _STD cmp_equal(_Static_extents[_Idx], _Other.extent(_Idx)), + "Value of other.extent(r) must be equal to extent(r) for each r for which extent(r) is a " + "static extent (N4950 [mdspan.extents.cons]/2.1)"); + } + _STL_VERIFY(_STD in_range(_Other.extent(_Idx)), + "Value of other.extent(r) must be representable as a value of type index_type for every rank index " + "r (N4950 [mdspan.extents.cons]/2.2)"); + } + } +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + requires (tuple_size_v<_ExtsTuple> == _Rank_dynamic) + constexpr explicit extents(_Extents_from_tuple, _ExtsTuple _Tpl, index_sequence<_Indices...>) noexcept + : _Base{static_cast(_STD move(_STD get<_Indices>(_Tpl)))...} {} + + template + requires (tuple_size_v<_ExtsTuple> != _Rank_dynamic) + constexpr explicit extents(_Extents_from_tuple, _ExtsTuple _Tpl, index_sequence<_DynIndices...>) noexcept + : _Base{static_cast(_STD move(_STD get<_Dynamic_indices_inv[_DynIndices]>(_Tpl)))...} { +#if _CONTAINER_DEBUG_LEVEL > 0 + [&](index_sequence<_MixedIndices...>) { + _STL_VERIFY(((_Static_extents[_MixedIndices] == dynamic_extent + || _STD cmp_equal(_Static_extents[_MixedIndices], + static_cast(_STD move(_STD get<_MixedIndices>(_Tpl))))) + && ...), + "Value of exts_arr[r] must be equal to extent(r) for each r for which extent(r) is a static extent " + "(N4950 [mdspan.extents.cons]/7.1)"); + }(make_index_sequence{}); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + constexpr explicit extents(span<_OtherIndexType, _Rank_dynamic> _Dynamic_exts, index_sequence<_Indices...>) noexcept + : _Base{static_cast(_STD as_const(_Dynamic_exts[_Indices]))...} { + _STL_INTERNAL_STATIC_ASSERT(is_convertible_v + && is_nothrow_constructible_v); +#if _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (_Is_standard_integer<_OtherIndexType> && _Rank_dynamic != 0) { + _STL_VERIFY(((_Dynamic_exts[_Indices] >= 0 && _STD in_range(_Dynamic_exts[_Indices])) && ...), + "exts[r] must be representable as a nonnegative value of type index_type for every rank index r " + "(N4950 [mdspan.extents.cons]/10.2)"); + } +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + constexpr explicit extents(span<_OtherIndexType, _Size> _Mixed_exts, index_sequence<_Indices...>) noexcept + : _Base{static_cast(_STD as_const(_Mixed_exts[_Dynamic_indices_inv[_Indices]]))...} { + _STL_INTERNAL_STATIC_ASSERT(_Size != _Rank_dynamic); + _STL_INTERNAL_STATIC_ASSERT(is_convertible_v + && is_nothrow_constructible_v); +#if _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (_Is_standard_integer<_OtherIndexType>) { + for (rank_type _Idx = 0; _Idx < _Rank; ++_Idx) { + _STL_VERIFY( + _Static_extents[_Idx] == dynamic_extent || _STD cmp_equal(_Static_extents[_Idx], _Mixed_exts[_Idx]), + "Value of exts[r] must be equal to extent(r) for each r for which extent(r) is a static extent " + "(N4950 [mdspan.extents.cons]/10.1)"); + _STL_VERIFY(_Mixed_exts[_Idx] >= 0 && _STD in_range(_Mixed_exts[_Idx]), + "exts[r] must be representable as a nonnegative value of type index_type for every rank index r " + "(N4950 [mdspan.extents.cons]/10.2)"); + } + } +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + +public: + _NODISCARD static constexpr rank_type rank() noexcept { + return _Rank; + } + + _NODISCARD static constexpr rank_type rank_dynamic() noexcept { + return _Rank_dynamic; + } + + _NODISCARD static constexpr size_t static_extent(const rank_type _Idx) noexcept { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Idx < _Rank, "Index must be less than rank() (N4950 [mdspan.extents.obs]/1)"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + return _Static_extents[_Idx]; + } + + _NODISCARD constexpr index_type extent(const rank_type _Idx) const noexcept { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Idx < _Rank, "Index must be less than rank() (N4950 [mdspan.extents.obs]/3)"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (rank_dynamic() == 0) { + return static_cast(_Static_extents[_Idx]); + } else if constexpr (rank_dynamic() == rank()) { + return this->_Array[_Idx]; + } else { + if (_Static_extents[_Idx] == dynamic_extent) { + return this->_Array[_Dynamic_indices[_Idx]]; + } else { + return static_cast(_Static_extents[_Idx]); + } + } + } + + constexpr extents() noexcept = default; + + template + requires (sizeof...(_OtherExtents) == rank()) + && ((_OtherExtents == dynamic_extent || _Extents == dynamic_extent || _OtherExtents == _Extents) && ...) + constexpr explicit(((_Extents != dynamic_extent && _OtherExtents == dynamic_extent) || ...) + || (numeric_limits::max)() < (numeric_limits<_OtherIndexType>::max)()) + extents(const extents<_OtherIndexType, _OtherExtents...>& _Other) noexcept + : extents(_Other, make_index_sequence{}) {} + + template + requires (is_convertible_v<_OtherIndexTypes, index_type> && ...) + && (is_nothrow_constructible_v && ...) + && (sizeof...(_OtherIndexTypes) == rank_dynamic() || sizeof...(_OtherIndexTypes) == rank()) + constexpr explicit extents(_OtherIndexTypes... _Exts) noexcept + : extents(_Extents_from_tuple{}, _STD tie(_Exts...), make_index_sequence{}) { +#if _CONTAINER_DEBUG_LEVEL > 0 + auto _Check_extent = [](const _Ty& _Ext) { + if constexpr (_Is_standard_integer<_Ty>) { + return _Ext >= 0 && _STD in_range(_Ext); + } else if constexpr (integral<_Ty> && !same_as<_Ty, bool>) { // NB: character types + const auto _Integer_ext = static_cast(_Ext); + return _Integer_ext >= 0 && _STD in_range(_Integer_ext); + } else { + return true; // NB: We cannot check preconditions + } + }; + _STL_VERIFY((_Check_extent(_Exts) && ...), "Each argument must be representable as a nonnegative value of type " + "index_type (N4950 [mdspan.extents.cons]/7.2)"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + requires is_convertible_v + && is_nothrow_constructible_v + && (_Size == rank_dynamic() || _Size == rank()) + constexpr explicit(_Size != rank_dynamic()) extents(span<_OtherIndexType, _Size> _Exts) noexcept + : extents(_Exts, make_index_sequence{}) {} + + template + requires is_convertible_v + && is_nothrow_constructible_v + && (_Size == rank_dynamic() || _Size == rank()) + constexpr explicit(_Size != rank_dynamic()) extents(const array<_OtherIndexType, _Size>& _Exts) noexcept + : extents(span{_Exts}, make_index_sequence{}) {} + + template + _NODISCARD_FRIEND constexpr bool operator==( + const extents& _Left, const extents<_OtherIndexType, _OtherExtents...>& _Right) noexcept { + if constexpr (rank() != sizeof...(_OtherExtents)) { + return false; + } else { + for (rank_type _Idx = 0; _Idx < _Rank; ++_Idx) { + if (_STD cmp_not_equal(_Left.extent(_Idx), _Right.extent(_Idx))) { + return false; + } + } + return true; + } + } + + _NODISCARD static consteval bool _Is_static_multidim_index_space_size_representable() noexcept { + // Pre: rank_dynamic() == 0 + if constexpr (_Multidim_index_space_size_is_always_zero) { + return true; + } else { + index_type _Result{1}; +#pragma warning(push) +#pragma warning(disable : 6287) // TRANSITION, DevCom-10398426 + const bool _Overflow = (_Mul_overflow(static_cast(_Extents), _Result, _Result) || ...); +#pragma warning(pop) + return !_Overflow; + } + } + + template + _NODISCARD constexpr bool _Is_dynamic_multidim_index_space_size_representable() const noexcept { + // Pre: rank_dynamic() != 0 + if constexpr (_Multidim_index_space_size_is_always_zero) { + return true; + } else { + bool _Overflow = false; + _Ty _Result = 1; + for (rank_type _Idx = 0; _Idx < _Rank; ++_Idx) { + const auto _Ext = static_cast<_Ty>(extent(_Idx)); + if (_Ext == 0) { + return true; + } + + if (!_Overflow) { + _Overflow = _Mul_overflow(_Ext, _Result, _Result); + } + } + + return !_Overflow; + } + } + + template + _NODISCARD constexpr bool _Contains_multidimensional_index( + index_sequence<_Seq...>, _IndexTypes... _Indices) const noexcept { + _STL_INTERNAL_STATIC_ASSERT((same_as<_IndexTypes, index_type> && ...)); + if constexpr (unsigned_integral) { + return ((_Indices < extent(_Seq)) && ...); + } else { + return ((0 <= _Indices && _Indices < extent(_Seq)) && ...); + } + } +}; + +template +inline constexpr size_t _Repeat_dynamic_extent = dynamic_extent; + +template + requires (is_convertible_v<_Integrals, size_t> && ...) +explicit extents(_Integrals...) -> extents...>; + +template +struct _Dextents_impl; + +template +struct _Dextents_impl<_IndexType, index_sequence<_Indices...>> { + using type = extents<_IndexType, ((void) _Indices, dynamic_extent)...>; +}; + +_EXPORT_STD template +using dextents = _Dextents_impl<_IndexType, make_index_sequence<_Rank>>::type; + +template +inline constexpr bool _Is_extents = false; + +template +inline constexpr bool _Is_extents> = true; + +template +class _Fwd_prod_of_extents { +public: + _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents>); + + _NODISCARD static constexpr _Extents::index_type _Calculate(const _Extents& _Exts, const size_t _Idx) noexcept { + _STL_INTERNAL_CHECK(_Idx <= _Extents::_Rank); + if constexpr (_Extents::rank() == 0) { + return 1; + } else { + typename _Extents::index_type _Result = 1; + for (size_t _Dim = 0; _Dim < _Idx; ++_Dim) { + _Result *= _Exts.extent(_Dim); + } + return _Result; + } + } +}; + +template + requires (_Extents::rank() > 0) && (_Extents::rank_dynamic() == 0) +class _Fwd_prod_of_extents<_Extents> { +private: + _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents>); + + _NODISCARD static consteval auto _Make_prods() noexcept { + array _Result; + _Result.front() = 1; + for (size_t _Idx = 1; _Idx < _Extents::_Rank + 1; ++_Idx) { + _Result[_Idx] = static_cast<_Extents::index_type>(_Result[_Idx - 1] * _Extents::_Static_extents[_Idx - 1]); + } + return _Result; + } + + static constexpr array _Cache = _Make_prods(); + +public: + _NODISCARD static constexpr _Extents::index_type _Calculate(const _Extents&, const size_t _Idx) noexcept { + _STL_INTERNAL_CHECK(_Idx <= _Extents::_Rank); + return _Cache[_Idx]; + } +}; + +template +class _Rev_prod_of_extents { +public: + _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents>); + _STL_INTERNAL_STATIC_ASSERT(_Extents::rank() > 0); + + _NODISCARD static constexpr _Extents::index_type _Calculate(const _Extents& _Exts, const size_t _Idx) noexcept { + _STL_INTERNAL_CHECK(_Idx < _Extents::_Rank); + typename _Extents::index_type _Result = 1; + for (size_t _Dim = _Idx + 1; _Dim < _Extents::_Rank; ++_Dim) { + _Result *= _Exts.extent(_Dim); + } + return _Result; + } +}; + +template + requires (_Extents::rank_dynamic() == 0) +class _Rev_prod_of_extents<_Extents> { +private: + _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents>); + _STL_INTERNAL_STATIC_ASSERT(_Extents::rank() > 0); + + _NODISCARD static consteval auto _Make_prods() noexcept { + array _Result; + _Result.back() = 1; + for (size_t _Idx = _Extents::_Rank; _Idx-- > 1;) { + _Result[_Idx - 1] = static_cast<_Extents::index_type>(_Result[_Idx] * _Extents::_Static_extents[_Idx]); + } + return _Result; + } + + static constexpr array _Cache = _Make_prods(); + +public: + _NODISCARD static constexpr _Extents::index_type _Calculate(const _Extents&, const size_t _Idx) noexcept { + _STL_INTERNAL_CHECK(_Idx < _Extents::_Rank); + return _Cache[_Idx]; + } +}; + +template +inline constexpr bool _Is_mapping_of = + is_same_v, _Mapping>; + +_EXPORT_STD struct layout_left { + template + class mapping; +}; + +_EXPORT_STD struct layout_right { + template + class mapping; +}; + +_EXPORT_STD struct layout_stride { + template + class mapping; +}; + +template +struct _Maybe_fully_static_extents { + _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents>); + + constexpr _Maybe_fully_static_extents() noexcept = default; + + template + constexpr explicit _Maybe_fully_static_extents(const _OtherExtents& _Exts_) : _Exts(_Exts_) {} + + _Extents _Exts{}; +}; + +template + requires (_Extents::rank_dynamic() == 0) +struct _Maybe_fully_static_extents<_Extents> { + _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents>); + + constexpr _Maybe_fully_static_extents() noexcept = default; + + template + constexpr explicit _Maybe_fully_static_extents([[maybe_unused]] const _OtherExtents& _Exts_) { +#if _CONTAINER_DEBUG_LEVEL > 0 + (void) _Extents{_Exts_}; // NB: temporary created for preconditions check +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + static constexpr _Extents _Exts{}; +}; + +template +class layout_left::mapping : private _Maybe_fully_static_extents<_Extents> { +public: + using extents_type = _Extents; + using index_type = extents_type::index_type; + using size_type = extents_type::size_type; + using rank_type = extents_type::rank_type; + using layout_type = layout_left; + +private: + using _Base = _Maybe_fully_static_extents; + + static_assert(_Is_extents, + "Extents must be a specialization of std::extents (N4950 [mdspan.layout.left.overview]/2)."); + static_assert( + extents_type::rank_dynamic() != 0 || extents_type::_Is_static_multidim_index_space_size_representable(), + "If Extents::rank_dynamic() == 0 is true, then the size of the multidimensional index space Extents() must be " + "representable as a value of type typename Extents::index_type (N4950 [mdspan.layout.left.overview]/4)."); + +public: + constexpr mapping() noexcept = default; + constexpr mapping(const mapping&) noexcept = default; + + constexpr mapping(const extents_type& _Exts_) noexcept : _Base(_Exts_) { +#if _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (extents_type::rank_dynamic() != 0) { + _STL_VERIFY(_Exts_._Is_dynamic_multidim_index_space_size_representable(), + "The size of the multidimensional index space e must be representable as a value of type index_type " + "(N4950 [mdspan.layout.left.cons]/1)."); + } +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + requires is_constructible_v + constexpr explicit(!is_convertible_v<_OtherExtents, extents_type>) + mapping(const mapping<_OtherExtents>& _Other) noexcept + : _Base(_Other.extents()) { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_STD in_range(_Other.required_span_size()), + "Value of other.required_span_size() must be representable as a value of type index_type (N4950 " + "[mdspan.layout.left.cons]/4)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + requires (extents_type::rank() <= 1) && is_constructible_v + constexpr explicit(!is_convertible_v<_OtherExtents, extents_type>) + mapping(const layout_right::mapping<_OtherExtents>& _Other) noexcept + : _Base(_Other.extents()) { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_STD in_range(_Other.required_span_size()), + "Value of other.required_span_size() must be representable as a value of type index_type (N4950 " + "[mdspan.layout.left.cons]/7)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + requires is_constructible_v + constexpr explicit(extents_type::rank() > 0) + mapping(const layout_stride::mapping<_OtherExtents>& _Other) noexcept // strengthened + : _Base(_Other.extents()) { +#if _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (extents_type::rank() > 0) { + index_type _Prod = 1; + for (size_t _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { + _STL_VERIFY(_Other.stride(_Idx) == _Prod, + "For all r in the range [0, extents_type::rank()), other.stride(r) must be equal to " + "extents().fwd-prod-of-extents(r) (N4950 [mdspan.layout.left.cons]/10.1)."); + _Prod = static_cast(_Prod * this->_Exts.extent(_Idx)); + } + } + _STL_VERIFY(_STD in_range(_Other.required_span_size()), + "Value of other.required_span_size() must be representable as a value of type index_type (N4950 " + "[mdspan.layout.left.cons]/10.2)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + constexpr mapping& operator=(const mapping&) noexcept = default; + + _NODISCARD constexpr const extents_type& extents() const noexcept { + return this->_Exts; + } + + _NODISCARD constexpr index_type required_span_size() const noexcept { + return _Fwd_prod_of_extents::_Calculate(this->_Exts, extents_type::_Rank); + } + + template + requires (sizeof...(_IndexTypes) == extents_type::rank()) && (is_convertible_v<_IndexTypes, index_type> && ...) + && (is_nothrow_constructible_v && ...) + _NODISCARD constexpr index_type operator()(_IndexTypes... _Indices) const noexcept { + return _Index_impl(make_index_sequence{}, static_cast(_Indices)...); + } + + _NODISCARD static constexpr bool is_always_unique() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_always_exhaustive() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_always_strided() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_unique() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_exhaustive() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_strided() noexcept { + return true; + } + + _NODISCARD constexpr index_type stride(const rank_type _Idx) const noexcept + requires (extents_type::rank() > 0) + { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Idx < extents_type::_Rank, + "Value of i must be less than extents_type::rank() (N4950 [mdspan.layout.left.obs]/6)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + return _Fwd_prod_of_extents::_Calculate(this->_Exts, _Idx); + } + + template + requires (extents_type::rank() == _OtherExtents::rank()) + _NODISCARD_FRIEND constexpr bool operator==(const mapping& _Left, const mapping<_OtherExtents>& _Right) noexcept { + return _Left._Exts == _Right.extents(); + } + +private: + template + _NODISCARD constexpr index_type _Index_impl( + [[maybe_unused]] index_sequence<_Seq...> _Index_seq, _IndexTypes... _Indices) const noexcept { + _STL_INTERNAL_STATIC_ASSERT((same_as<_IndexTypes, index_type> && ...)); +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(this->_Exts._Contains_multidimensional_index(_Index_seq, _Indices...), + "Value of extents_type::index-cast(i) must be a multidimensional index in extents_ (N4950 " + "[mdspan.layout.left.obs]/3)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + + index_type _Stride = 1; + index_type _Result = 0; + (((_Result += _Indices * _Stride), (_Stride *= this->_Exts.extent(_Seq))), ...); + return _Result; + } +}; + +template +class layout_right::mapping : private _Maybe_fully_static_extents<_Extents> { +public: + using extents_type = _Extents; + using index_type = extents_type::index_type; + using size_type = extents_type::size_type; + using rank_type = extents_type::rank_type; + using layout_type = layout_right; + +private: + using _Base = _Maybe_fully_static_extents; + + static_assert(_Is_extents, + "Extents must be a specialization of std::extents (N4950 [mdspan.layout.right.overview]/2)."); + static_assert( + extents_type::rank_dynamic() != 0 || extents_type::_Is_static_multidim_index_space_size_representable(), + "If Extents::rank_dynamic() == 0 is true, then the size of the multidimensional index space Extents() must be " + "representable as a value of type typename Extents::index_type (N4950 [mdspan.layout.right.overview]/4)."); + +public: + constexpr mapping() noexcept = default; + constexpr mapping(const mapping&) noexcept = default; + + constexpr mapping(const extents_type& _Exts_) noexcept : _Base(_Exts_) { +#if _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (extents_type::rank_dynamic() != 0) { + _STL_VERIFY(_Exts_._Is_dynamic_multidim_index_space_size_representable(), + "The size of the multidimensional index space e must be representable as a value of type index_type " + "(N4950 [mdspan.layout.right.cons]/1)."); + } +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + requires is_constructible_v + constexpr explicit(!is_convertible_v<_OtherExtents, extents_type>) + mapping(const mapping<_OtherExtents>& _Other) noexcept + : _Base(_Other.extents()) { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_STD in_range(_Other.required_span_size()), + "Value of other.required_span_size() must be representable as a value of type index_type (N4950 " + "[mdspan.layout.right.cons]/4)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + requires (extents_type::rank() <= 1) && is_constructible_v + constexpr explicit(!is_convertible_v<_OtherExtents, extents_type>) + mapping(const layout_left::mapping<_OtherExtents>& _Other) noexcept + : _Base(_Other.extents()) { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_STD in_range(_Other.required_span_size()), + "Value of other.required_span_size() must be representable as a value of type index_type (N4950 " + "[mdspan.layout.right.cons]/7)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + template + requires is_constructible_v + constexpr explicit(extents_type::rank() > 0) mapping(const layout_stride::mapping<_OtherExtents>& _Other) noexcept + : _Base(_Other.extents()) { +#if _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (extents_type::rank() > 0) { + index_type _Prod = 1; + for (size_t _Idx = extents_type::_Rank; _Idx-- > 0;) { + _STL_VERIFY(_Prod == _Other.stride(_Idx), + "For all r in the range [0, extents_type::rank()), other.stride(r) must be equal to " + "extents().rev-prod-of-extents(r) (N4950 [mdspan.layout.right.cons]/10.1)."); + _Prod = static_cast(_Prod * this->_Exts.extent(_Idx)); + } + } + _STL_VERIFY(_STD in_range(_Other.required_span_size()), + "Value of other.required_span_size() must be representable as a value of type index_type (N4950 " + "[mdspan.layout.right.cons]/10.2)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + constexpr mapping& operator=(const mapping&) noexcept = default; + + _NODISCARD constexpr const extents_type& extents() const noexcept { + return this->_Exts; + } + + _NODISCARD constexpr index_type required_span_size() const noexcept { + return _Fwd_prod_of_extents::_Calculate(this->_Exts, extents_type::_Rank); + } + + template + requires (sizeof...(_IndexTypes) == extents_type::rank()) && (is_convertible_v<_IndexTypes, index_type> && ...) + && (is_nothrow_constructible_v && ...) + _NODISCARD constexpr index_type operator()(_IndexTypes... _Indices) const noexcept { + return _Index_impl(make_index_sequence{}, static_cast(_Indices)...); + } + + _NODISCARD static constexpr bool is_always_unique() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_always_exhaustive() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_always_strided() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_unique() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_exhaustive() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_strided() noexcept { + return true; + } + + _NODISCARD constexpr index_type stride(const rank_type _Idx) const noexcept + requires (extents_type::rank() > 0) + { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Idx < extents_type::_Rank, + "Value of i must be less than extents_type::rank() (N4950 [mdspan.layout.right.obs]/6)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + return _Rev_prod_of_extents::_Calculate(this->_Exts, _Idx); + } + + template + requires (extents_type::rank() == _OtherExtents::rank()) + _NODISCARD_FRIEND constexpr bool operator==(const mapping& _Left, const mapping<_OtherExtents>& _Right) noexcept { + return _Left._Exts == _Right.extents(); + } + +private: + template + _NODISCARD constexpr index_type _Index_impl( + [[maybe_unused]] index_sequence<_Seq...> _Index_seq, _IndexTypes... _Indices) const noexcept { + _STL_INTERNAL_STATIC_ASSERT((same_as<_IndexTypes, index_type> && ...)); +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(this->_Exts._Contains_multidimensional_index(_Index_seq, _Indices...), + "Value of extents_type::index-cast(i) must be a multidimensional index in extents_ (N4950 " + "[mdspan.layout.right.obs]/3)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + + index_type _Result = 0; + ((_Result = static_cast(_Indices + this->_Exts.extent(_Seq) * _Result)), ...); + return _Result; + } +}; + +template +concept _Layout_mapping_alike = requires { + requires _Is_extents; + { _Mp::is_always_strided() } -> same_as; + { _Mp::is_always_exhaustive() } -> same_as; + { _Mp::is_always_unique() } -> same_as; + bool_constant<_Mp::is_always_strided()>::value; + bool_constant<_Mp::is_always_exhaustive()>::value; + bool_constant<_Mp::is_always_unique()>::value; +}; + +template +class layout_stride::mapping : private _Maybe_fully_static_extents<_Extents>, + private _Maybe_empty_array { +public: + using extents_type = _Extents; + using index_type = extents_type::index_type; + using size_type = extents_type::size_type; + using rank_type = extents_type::rank_type; + using layout_type = layout_stride; + +private: + using _Extents_base = _Maybe_fully_static_extents; + using _Strides_base = _Maybe_empty_array; + + static_assert(_Is_extents, + "Extents must be a specialization of std::extents (N4950 [mdspan.layout.stride.overview]/2)."); + static_assert( + extents_type::rank_dynamic() != 0 || extents_type::_Is_static_multidim_index_space_size_representable(), + "If Extents::rank_dynamic() == 0 is true, then the size of the multidimensional index space Extents() must be " + "representable as a value of type typename Extents::index_type (N4950 [mdspan.layout.stride.overview]/4)."); + + template + constexpr mapping(const extents_type& _Exts_, span<_OtherIndexType, extents_type::rank()> _Strides_, + index_sequence<_Indices...>) noexcept + : _Extents_base(_Exts_), _Strides_base{static_cast(_STD as_const(_Strides_[_Indices]))...} { + _STL_INTERNAL_STATIC_ASSERT(is_convertible_v + && is_nothrow_constructible_v); +#if _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (extents_type::rank() != 0) { + bool _Found_zero = false; + bool _Overflow = false; + index_type _Req_span_size = 0; + for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { + const index_type _Stride = this->_Array[_Idx]; + _STL_VERIFY(_Stride > 0, "Value of s[i] must be greater than 0 for all i in the range [0, rank_) " + "(N4950 [mdspan.layout.stride.cons]/4.1)."); + const index_type _Ext = this->_Exts.extent(_Idx); + if (_Ext == 0) { + _Found_zero = true; + } + + if (!_Found_zero && !_Overflow) { + index_type _Prod; + _Overflow = _Mul_overflow(static_cast(_Ext - 1), _Stride, _Prod) + || _Add_overflow(_Req_span_size, _Prod, _Req_span_size); + } + } + _STL_VERIFY(_Found_zero || !_Overflow, "REQUIRED-SPAN-SIZE(e, s) must be representable as a value of type " + "index_type (N4950 [mdspan.layout.stride.cons]/4.2)."); + } +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + +public: + constexpr mapping() noexcept : _Extents_base(extents_type{}) { + if constexpr (extents_type::rank() != 0) { + this->_Array.back() = 1; + for (rank_type _Idx = extents_type::_Rank - 1; _Idx-- > 0;) { +#if _CONTAINER_DEBUG_LEVEL > 0 + const bool _Overflow = + _Mul_overflow(this->_Array[_Idx + 1], this->_Exts.extent(_Idx + 1), this->_Array[_Idx]); + // NB: N4950 requires value of 'layout_right::mapping().required_span_size()' to be + // representable as a value of type 'index_type', but this is not enough. We need to require every + // single stride to be representable as a value of type 'index_type', so we can get desired effects. + _STL_VERIFY(!_Overflow, + "Value of layout_right::mapping().required_span_size() must be " + "representable as a value of type index_type (N4950 [mdspan.layout.stride.cons]/1)."); +#else // ^^^ _CONTAINER_DEBUG_LEVEL > 0 / _CONTAINER_DEBUG_LEVEL == 0 vvv + this->_Array[_Idx] = static_cast(this->_Array[_Idx + 1] * this->_Exts.extent(_Idx + 1)); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + } + } + + constexpr mapping(const mapping&) noexcept = default; + + // TRANSITION, VSO-1852021: `_Extents::rank()` should be `extents_type::rank()` + // TRANSITION, VSO-1852030: `typename _Extents::index_type` should be `index_type` + template + requires is_convertible_v + && is_nothrow_constructible_v + constexpr mapping(const extents_type& _Exts_, span<_OtherIndexType, _Extents::rank()> _Strides_) noexcept + : mapping(_Exts_, _Strides_, make_index_sequence{}) {} + + // TRANSITION, VSO-1852021: `_Extents::rank()` should be `extents_type::rank()` + // TRANSITION, VSO-1852030: `typename _Extents::index_type` should be `index_type` + template + requires is_convertible_v + && is_nothrow_constructible_v + constexpr mapping(const extents_type& _Exts_, const array<_OtherIndexType, _Extents::rank()>& _Strides_) noexcept + : mapping(_Exts_, span{_Strides_}, make_index_sequence{}) {} + + template + requires _Layout_mapping_alike<_StridedLayoutMapping> + && is_constructible_v + && (_StridedLayoutMapping::is_always_unique()) && (_StridedLayoutMapping::is_always_strided()) + constexpr explicit(!( + is_convertible_v + && (_Is_mapping_of || _Is_mapping_of + || _Is_mapping_of) )) + mapping(const _StridedLayoutMapping& _Other) noexcept + : _Extents_base(_Other.extents()) { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_STD in_range(_Other.required_span_size()), + "Value of other.required_span_size() must be representable as a value of type index_type (N4950 " + "[mdspan.layout.stride.cons]/7.3)."); + _STL_VERIFY( + _Offset(_Other) == 0, "Value of OFFSET(other) must be equal to 0 (N4950 [mdspan.layout.stride.cons]/7.4)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (extents_type::_Rank != 0) { + for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { + const auto _Stride = _Other.stride(_Idx); +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Stride > 0, "Value of other.stride(r) must be greater than 0 for every rank index r of " + "extents() (N4950 [mdspan.layout.stride.cons]/7.2)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + this->_Array[_Idx] = static_cast(_Stride); + } + } + } + + constexpr mapping& operator=(const mapping&) noexcept = default; + + _NODISCARD constexpr const extents_type& extents() const noexcept { + return this->_Exts; + } + + _NODISCARD constexpr array strides() const noexcept { + if constexpr (extents_type::rank() == 0) { + return {}; + } else { + return this->_Array; + } + } + + _NODISCARD constexpr index_type required_span_size() const noexcept { + if constexpr (extents_type::rank() == 0) { + return 1; + } else { + index_type _Result = 1; + for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { + const index_type _Ext = this->_Exts.extent(_Idx); + if (_Ext == 0) { + return 0; + } + + _Result += (_Ext - 1) * this->_Array[_Idx]; + } + + return _Result; + } + } + + template + requires (sizeof...(_IndexTypes) == extents_type::rank()) && (is_convertible_v<_IndexTypes, index_type> && ...) + && (is_nothrow_constructible_v && ...) + _NODISCARD constexpr index_type operator()(_IndexTypes... _Indices) const noexcept { + return _Index_impl(make_index_sequence{}, static_cast(_Indices)...); + } + + _NODISCARD static constexpr bool is_always_unique() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_always_exhaustive() noexcept { + return false; + } + + _NODISCARD static constexpr bool is_always_strided() noexcept { + return true; + } + + _NODISCARD static constexpr bool is_unique() noexcept { + return true; + } + + _NODISCARD constexpr bool is_exhaustive() const noexcept { + if constexpr (extents_type::rank() == 0) { + return true; + } else { + return required_span_size() + == _Fwd_prod_of_extents::_Calculate(this->_Exts, extents_type::_Rank); + } + } + + _NODISCARD static constexpr bool is_strided() noexcept { + return true; + } + + _NODISCARD constexpr index_type stride(const rank_type _Idx) const noexcept { + if constexpr (extents_type::rank() == 0) { + _STL_VERIFY(false, "The argument to stride must be nonnegative and less than extents_type::rank()."); + } else { + return this->_Array[_Idx]; + } + } + + template + requires _Layout_mapping_alike<_OtherMapping> && (extents_type::rank() == _OtherMapping::extents_type::rank()) + && (_OtherMapping::is_always_strided()) + _NODISCARD_FRIEND constexpr bool operator==(const mapping& _Left, const _OtherMapping& _Right) noexcept { + if constexpr (extents_type::rank() != 0) { + if (_Left.extents() != _Right.extents()) { + return false; + } + + for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { + if (_STD cmp_not_equal(_Left.stride(_Idx), _Right.stride(_Idx))) { + return false; + } + } + } + + return _Offset(_Right) == 0; + } + +private: + template + _NODISCARD static constexpr _OtherMapping::index_type _Offset(_OtherMapping& _Mapping) noexcept { + if constexpr (extents_type::rank() == 0) { + return _Mapping(); + } else { + for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { + if (_Mapping.extents().extent(_Idx) == 0) { + return 0; + } + } + + return [&](index_sequence<_Indices...>) { return _Mapping(((void) _Indices, 0)...); }( + make_index_sequence{}); + } + } + + template + _NODISCARD constexpr index_type _Index_impl( + [[maybe_unused]] index_sequence<_Seq...> _Index_seq, _IndexTypes... _Indices) const noexcept { + _STL_INTERNAL_STATIC_ASSERT((same_as<_IndexTypes, index_type> && ...)); +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(this->_Exts._Contains_multidimensional_index(_Index_seq, _Indices...), + "Value of extents_type::index-cast(i) must be a multidimensional index in extents_ (N4950 " + "[mdspan.layout.stride.obs]/3)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + + return static_cast(((_Indices * this->_Array[_Seq]) + ... + 0)); + } +}; + +_EXPORT_STD template +struct default_accessor { + using offset_policy = default_accessor; + using element_type = _ElementType; + using reference = _ElementType&; + using data_handle_type = _ElementType*; + + static_assert( + sizeof(element_type) > 0, "ElementType must be a complete type (N4950 [mdspan.accessor.default.overview]/2)."); + static_assert(!is_abstract_v, + "ElementType cannot be an abstract type (N4950 [mdspan.accessor.default.overview]/2)."); + static_assert( + !is_array_v, "ElementType cannot be an array type (N4950 [mdspan.accessor.default.overview]/2)."); + + constexpr default_accessor() noexcept = default; + + template + requires is_convertible_v<_OtherElementType (*)[], element_type (*)[]> + constexpr default_accessor(default_accessor<_OtherElementType>) noexcept {} + + _NODISCARD constexpr reference access(data_handle_type _Ptr, size_t _Idx) const noexcept { + return _Ptr[_Idx]; + } + + _NODISCARD constexpr data_handle_type offset(data_handle_type _Ptr, size_t _Idx) const noexcept { + return _Ptr + _Idx; + } +}; + +template +concept _Elidable_layout_mapping = + (_Is_any_of_v<_LayoutPolicy, layout_left, layout_right> && _Extents::rank_dynamic() == 0) + || (same_as<_LayoutPolicy, layout_stride> && _Extents::rank() == 0); + +template +struct _Mdspan_mapping_base { + _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents>); + + using _Mapping = _LayoutPolicy::template mapping<_Extents>; + + constexpr _Mdspan_mapping_base() noexcept = default; + + constexpr explicit _Mdspan_mapping_base(const _Extents& _Exts) : _Map(_Exts) {} + + template + constexpr explicit _Mdspan_mapping_base(const _OtherMapping& _Map_) : _Map(_Map_) {} + + _Mapping _Map = _Mapping(); +}; + +template _LayoutPolicy> +struct _Mdspan_mapping_base<_Extents, _LayoutPolicy> { + _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents>); + + using _Mapping = _LayoutPolicy::template mapping<_Extents>; + + constexpr _Mdspan_mapping_base() noexcept = default; + + constexpr explicit _Mdspan_mapping_base(const _Extents&) noexcept {} + + template + constexpr explicit _Mdspan_mapping_base(const _OtherMapping& _Map_) { + // NB: Constructing _Mapping from _OtherMapping may have side effects - we should create a temporary. + if constexpr (!_Elidable_layout_mapping) { + (void) _Mapping{_Map_}; + } + } + + static constexpr _Mapping _Map{}; +}; + +template +concept _Elidable_accessor_policy = _Is_specialization_v<_AccessorPolicy, default_accessor>; + +template +struct _Mdspan_accessor_base { + constexpr _Mdspan_accessor_base() noexcept = default; + + template + constexpr explicit _Mdspan_accessor_base(const _OtherAccessorPolicy& _Acc_) : _Acc(_Acc_) {} + + _AccessorPolicy _Acc = _AccessorPolicy(); +}; + +template <_Elidable_accessor_policy _AccessorPolicy> +struct _Mdspan_accessor_base<_AccessorPolicy> { + constexpr _Mdspan_accessor_base() noexcept = default; + + template + constexpr explicit _Mdspan_accessor_base(const _OtherAccessorPolicy& _Acc_) { + // NB: Constructing _AccessorPolicy from _OtherAccessorPolicy may have side effects - we should create a + // temporary. + if constexpr (!_Elidable_accessor_policy<_OtherAccessorPolicy>) { + (void) _AccessorPolicy{_Acc_}; + } + } + + static constexpr _AccessorPolicy _Acc{}; +}; + +_EXPORT_STD template > +class __declspec(empty_bases) mdspan : private _Mdspan_mapping_base<_Extents, _LayoutPolicy>, + private _Mdspan_accessor_base<_AccessorPolicy> { +public: + using extents_type = _Extents; + using layout_type = _LayoutPolicy; + using accessor_type = _AccessorPolicy; + using mapping_type = layout_type::template mapping; + using element_type = _ElementType; + using value_type = remove_cv_t; + using index_type = extents_type::index_type; + using size_type = extents_type::size_type; + using rank_type = extents_type::rank_type; + using data_handle_type = accessor_type::data_handle_type; + using reference = accessor_type::reference; + +private: + using _Mapping_base = _Mdspan_mapping_base; + using _Accessor_base = _Mdspan_accessor_base; + + static_assert( + sizeof(element_type) > 0, "ElementType must be a complete type (N4950 [mdspan.mdspan.overview]/2.1)."); + static_assert( + !is_abstract_v, "ElementType cannot be an abstract type (N4950 [mdspan.mdspan.overview]/2.1)."); + static_assert( + !is_array_v, "ElementType cannot be an array type (N4950 [mdspan.mdspan.overview]/2.1)."); + static_assert(_Is_extents, + "Extents must be a specialization of std::extents (N4950 [mdspan.mdspan.overview]/2.2)."); + static_assert(is_same_v, + "ElementType and typename AccessorPolicy::element_type must be the same type (N4950 " + "[mdspan.mdspan.overview]/2.3)."); + +public: + _NODISCARD static constexpr rank_type rank() noexcept { + return extents_type::_Rank; + } + + _NODISCARD static constexpr rank_type rank_dynamic() noexcept { + return extents_type::_Rank_dynamic; + } + + _NODISCARD static constexpr size_t static_extent(const rank_type _Idx) noexcept { +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(_Idx < extents_type::_Rank, "Index must be less than rank() (N4950 [mdspan.extents.obs]/1)"); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + return extents_type::_Static_extents[_Idx]; + } + + _NODISCARD constexpr index_type extent(const rank_type _Idx) const noexcept { + return this->_Map.extents().extent(_Idx); + } + + constexpr mdspan() noexcept( + is_nothrow_default_constructible_v&& is_nothrow_default_constructible_v&& + is_nothrow_default_constructible_v) // strengthened + requires (rank_dynamic() > 0) && is_default_constructible_v + && is_default_constructible_v && is_default_constructible_v + {} + + constexpr mdspan(const mdspan&) = default; + constexpr mdspan(mdspan&&) = default; + + template + requires (is_convertible_v<_OtherIndexTypes, index_type> && ...) + && (is_nothrow_constructible_v && ...) + && (sizeof...(_OtherIndexTypes) == rank() || sizeof...(_OtherIndexTypes) == rank_dynamic()) + && is_constructible_v && is_default_constructible_v + constexpr explicit mdspan(data_handle_type _Ptr_, _OtherIndexTypes... _Exts) noexcept( + is_nothrow_constructible_v&& + is_nothrow_default_constructible_v) // strengthened + : _Mapping_base(extents_type{static_cast(_STD move(_Exts))...}), _Accessor_base(), + _Ptr(_STD move(_Ptr_)) {} + + template + requires is_convertible_v + && 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) noexcept(is_nothrow_constructible_v&& + is_nothrow_default_constructible_v) // strengthened + : _Mapping_base(extents_type{_Exts}), _Accessor_base(), _Ptr(_STD move(_Ptr_)) {} + + template + requires is_convertible_v + && 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_, + const array<_OtherIndexType, _Size>& _Exts) noexcept(is_nothrow_constructible_v&& + is_nothrow_default_constructible_v) // strengthened + : _Mapping_base(extents_type{_Exts}), _Accessor_base(), _Ptr(_STD move(_Ptr_)) {} + + constexpr mdspan(data_handle_type _Ptr_, const extents_type& _Exts) noexcept( + is_nothrow_constructible_v&& is_nothrow_default_constructible_v) // strengthened + requires is_constructible_v && is_default_constructible_v + : _Mapping_base(_Exts), _Accessor_base(), _Ptr(_STD move(_Ptr_)) {} + + constexpr mdspan(data_handle_type _Ptr_, const mapping_type& _Map_) noexcept( + is_nothrow_copy_constructible_v&& + is_nothrow_default_constructible_v) // strengthened + requires is_default_constructible_v + : _Mapping_base(_Map_), _Accessor_base(), _Ptr(_STD move(_Ptr_)) {} + + constexpr mdspan(data_handle_type _Ptr_, const mapping_type& _Map_, const accessor_type& _Acc_) noexcept( + is_nothrow_copy_constructible_v&& is_nothrow_copy_constructible_v) // strengthened + : _Mapping_base(_Map_), _Accessor_base(_Acc_), _Ptr(_STD move(_Ptr_)) {} + + template + requires is_constructible_v&> + && is_constructible_v + constexpr explicit( + !is_convertible_v&, mapping_type> + || !is_convertible_v) + mdspan(const mdspan<_OtherElementType, _OtherExtents, _OtherLayoutPolicy, _OtherAccessor>& _Other) noexcept( + is_nothrow_constructible_v&& + is_nothrow_constructible_v&>&& + is_nothrow_constructible_v) // strengthened + : _Mapping_base(_Other.mapping()), _Accessor_base(_Other.accessor()), _Ptr(_Other.data_handle()) { + 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)."); + static_assert(is_constructible_v, + "The extents_type must be constructible from OtherExtents (N4950 [mdspan.mdspan.cons]/20.2)."); +#if _CONTAINER_DEBUG_LEVEL > 0 + for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { + const auto _Static_ext = extents_type::_Static_extents[_Idx]; + _STL_VERIFY(_STD cmp_equal(_Static_ext, dynamic_extent) || _STD cmp_equal(_Static_ext, _Other.extent(_Idx)), + "For each rank index r of extents_type, static_extent(r) == dynamic_extent || static_extent(r) == " + "other.extent(r) must be true (N4950 [mdspan.mdspan.cons]/21.1)."); + } +#endif // _CONTAINER_DEBUG_LEVEL > 0 + } + + constexpr mdspan& operator=(const mdspan&) = default; + constexpr mdspan& operator=(mdspan&&) = default; + +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 + template + requires (is_convertible_v<_OtherIndexTypes, index_type> && ...) + && (is_nothrow_constructible_v && ...) + && (sizeof...(_OtherIndexTypes) == rank()) + _NODISCARD constexpr reference operator[](_OtherIndexTypes... _Indices) const + noexcept(noexcept(_Access_impl(static_cast(_STD move(_Indices))...))) /* strengthened */ { + return _Access_impl(static_cast(_STD move(_Indices))...); + } +#endif // ^^^ defined(__cpp_multidimensional_subscript) ^^^ + +private: + template + _NODISCARD constexpr reference _Multidimensional_subscript( + span<_OtherIndexType, rank()> _Indices, index_sequence<_Seq...>) const + noexcept(noexcept(_Access_impl(static_cast(_STD as_const(_Indices[_Seq]))...))) { + return _Access_impl(static_cast(_STD as_const(_Indices[_Seq]))...); + } + +public: + template + requires is_convertible_v + && is_nothrow_constructible_v + _NODISCARD constexpr reference operator[](span<_OtherIndexType, rank()> _Indices) const + noexcept(noexcept(_Multidimensional_subscript(_Indices, make_index_sequence{}))) /* strengthened */ + { + return _Multidimensional_subscript(_Indices, make_index_sequence{}); + } + + template + requires is_convertible_v + && is_nothrow_constructible_v + _NODISCARD constexpr reference operator[](const array<_OtherIndexType, rank()>& _Indices) const noexcept( + noexcept(_Multidimensional_subscript(span{_Indices}, make_index_sequence{}))) /* strengthened */ + { + return _Multidimensional_subscript(span{_Indices}, make_index_sequence{}); + } + + _NODISCARD constexpr size_type size() const noexcept { +#if _CONTAINER_DEBUG_LEVEL > 0 + if constexpr (rank_dynamic() != 0) { + _STL_VERIFY(this->_Map.extents().template _Is_dynamic_multidim_index_space_size_representable(), + "The size of the multidimensional index space extents() must be representable as a value of type " + "size_type (N4950 [mdspan.mdspan.members]/7)."); + } +#endif // _CONTAINER_DEBUG_LEVEL > 0 + return static_cast( + _Fwd_prod_of_extents::_Calculate(this->_Map.extents(), extents_type::_Rank)); + } + + _NODISCARD constexpr bool empty() const noexcept { + if constexpr (extents_type::_Multidim_index_space_size_is_always_zero) { + return true; + } else { + const extents_type& _Exts = this->_Map.extents(); + for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { + if (_Exts.extent(_Idx) == 0) { + return true; + } + } + return false; + } + } + + friend constexpr void swap(mdspan& _Left, mdspan& _Right) noexcept { + swap(_Left._Ptr, _Right._Ptr); // intentional ADL + + if constexpr (!_Elidable_layout_mapping) { + swap(_Left._Map, _Right._Map); // intentional ADL + } + + if constexpr (!_Elidable_accessor_policy) { + swap(_Left._Acc, _Right._Acc); // intentional ADL + } + } + + _NODISCARD constexpr const extents_type& extents() const noexcept { + return this->_Map.extents(); + } + + _NODISCARD constexpr const data_handle_type& data_handle() const noexcept { + return _Ptr; + } + + _NODISCARD constexpr const mapping_type& mapping() const noexcept { + return this->_Map; + } + + _NODISCARD constexpr const accessor_type& accessor() const noexcept { + return this->_Acc; + } + + _NODISCARD static constexpr bool is_always_unique() noexcept /* strengthened */ { + constexpr bool _Result = mapping_type::is_always_unique(); + return _Result; + } + + _NODISCARD static constexpr bool is_always_exhaustive() noexcept /* strengthened */ { + constexpr bool _Result = mapping_type::is_always_exhaustive(); + return _Result; + } + + _NODISCARD static constexpr bool is_always_strided() noexcept /* strengthened */ { + constexpr bool _Result = mapping_type::is_always_strided(); + return _Result; + } + + _NODISCARD constexpr bool is_unique() const noexcept(noexcept(this->_Map.is_unique())) /* strengthened */ { + return this->_Map.is_unique(); + } + + _NODISCARD constexpr bool is_exhaustive() const noexcept(noexcept(this->_Map.is_exhaustive())) /* strengthened */ { + return this->_Map.is_exhaustive(); + } + + _NODISCARD constexpr bool is_strided() const noexcept(noexcept(this->_Map.is_strided())) /* strengthened */ { + return this->_Map.is_strided(); + } + + _NODISCARD constexpr index_type stride(const rank_type _Idx) const + noexcept(noexcept(this->_Map.stride(_Idx))) /* strengthened */ { + return this->_Map.stride(_Idx); + } + +private: + template + _NODISCARD constexpr reference _Access_impl(_OtherIndexTypes... _Indices) const + noexcept(noexcept(this->_Acc.access(_Ptr, static_cast(this->_Map(_Indices...))))) { + _STL_INTERNAL_STATIC_ASSERT((same_as<_OtherIndexTypes, index_type> && ...)); +#if _CONTAINER_DEBUG_LEVEL > 0 + _STL_VERIFY(this->_Map.extents()._Contains_multidimensional_index(make_index_sequence{}, _Indices...), + "I must be a multidimensional index in extents() (N4950 [mdspan.mdspan.members]/3)."); +#endif // _CONTAINER_DEBUG_LEVEL > 0 + + return this->_Acc.access(_Ptr, static_cast(this->_Map(_Indices...))); + } + + /* [[no_unique_address]] */ data_handle_type _Ptr = data_handle_type(); +}; + +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, dextents>; + +template +mdspan(_ElementType*, span<_OtherIndexType, _Nx>) -> mdspan<_ElementType, dextents>; + +template +mdspan(_ElementType*, const array<_OtherIndexType, _Nx>&) -> 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; + +_STD_END + +// TRANSITION, non-_Ugly attribute tokens +#pragma pop_macro("empty_bases") + +#pragma pop_macro("new") +_STL_RESTORE_CLANG_WARNINGS +#pragma warning(pop) +#pragma pack(pop) +#endif // ^^^ supported language mode ^^^ +#endif // _STL_COMPILER_PREPROCESSOR +#endif // _MDSPAN_ diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 09d2cc7eaa9..54798c33960 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -315,6 +315,7 @@ // Other C++20 deprecation warnings // _HAS_CXX23 directly controls: +// P0009R18 // P0288R9 move_only_function // P0323R12 // P0401R6 Providing Size Feedback In The Allocator Interface @@ -374,9 +375,13 @@ // P2539R4 Synchronizing print() With The Underlying Stream // P2540R1 Empty Product For Certain Views // P2549R1 unexpected::error() +// P2599R2 mdspan: index_type, size_type +// P2604R0 mdspan: data_handle_type, data_handle(), exhaustive +// P2613R1 mdspan: empty() // P2652R2 Disallowing User Specialization Of allocator_traits // P2693R1 Formatting thread::id And stacktrace // P2713R1 Escaping Improvements In std::format +// P2763R1 Fixing layout_stride's Default Constructor For Fully Static Extents // _HAS_CXX23 and _SILENCE_ALL_CXX23_DEPRECATION_WARNINGS control: // P1413R3 Deprecate aligned_storage And aligned_union @@ -1800,10 +1805,15 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect #define __cpp_lib_formatters 202302L #endif // defined(__cpp_lib_concepts) -#define __cpp_lib_forward_like 202207L -#define __cpp_lib_invoke_r 202106L -#define __cpp_lib_ios_noreplace 202207L -#define __cpp_lib_is_scoped_enum 202011L +#define __cpp_lib_forward_like 202207L +#define __cpp_lib_invoke_r 202106L +#define __cpp_lib_ios_noreplace 202207L +#define __cpp_lib_is_scoped_enum 202011L + +#ifdef __cpp_lib_concepts +#define __cpp_lib_mdspan 202207L +#endif // defined(__cpp_lib_concepts) + #define __cpp_lib_move_only_function 202110L #ifdef __cpp_lib_concepts diff --git a/stl/modules/std.ixx b/stl/modules/std.ixx index fed223a5fcd..7a5fdbf7cef 100644 --- a/stl/modules/std.ixx +++ b/stl/modules/std.ixx @@ -78,6 +78,9 @@ export module std; #include #include #include +#if _HAS_CXX23 +#include +#endif // _HAS_CXX23 #include #include #include diff --git a/tests/std/include/test_header_units_and_modules.hpp b/tests/std/include/test_header_units_and_modules.hpp index 41b75f4d3bb..828a3e9bbc4 100644 --- a/tests/std/include/test_header_units_and_modules.hpp +++ b/tests/std/include/test_header_units_and_modules.hpp @@ -391,6 +391,19 @@ void test_map() { assert(m[30] == 33); } +#if TEST_STANDARD >= 23 +void test_mdspan() { + using namespace std; + puts("Testing ."); + int arr[] = {10, 0, 0, 0, 20, 0, 0, 0, 30}; + layout_right::mapping> mp; + assert(arr[mp(0, 0)] == 10); + assert(arr[mp(1, 1)] == 20); + assert(arr[mp(2, 2)] == 30); + // TRANSITION, test std::mdspan too (DevCom-10359857) +} +#endif // TEST_STANDARD >= 23 + void test_memory() { using namespace std; puts("Testing ."); @@ -1111,6 +1124,9 @@ void all_cpp_header_tests() { test_list(); test_locale(); test_map(); +#if TEST_STANDARD >= 23 + test_mdspan(); +#endif // TEST_STANDARD >= 23 test_memory(); test_memory_resource(); test_mutex(); diff --git a/tests/std/include/test_mdspan_support.hpp b/tests/std/include/test_mdspan_support.hpp new file mode 100644 index 00000000000..12b94e4bb7b --- /dev/null +++ b/tests/std/include/test_mdspan_support.hpp @@ -0,0 +1,333 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum class IsExplicit : bool { no, yes }; +enum class IsNothrow : bool { no, yes }; + +template +struct ConvertibleToInt { + Int val = 1; + + constexpr explicit(std::to_underlying(Explicit)) operator Int() const noexcept(std::to_underlying(Nothrow)) { + return val; + } +}; + +static_assert(std::is_aggregate_v>); +static_assert(std::is_convertible_v, int>); +static_assert(std::is_nothrow_convertible_v, int>); +static_assert(!std::is_nothrow_convertible_v, int>); +static_assert(std::is_convertible_v, int>); +static_assert(!std::is_convertible_v, int>); + +template +struct NonConstConvertibleToInt { + operator Int() noexcept; // not defined +}; + +static_assert(std::is_convertible_v, int>); +static_assert(!std::is_convertible_v, int>); +static_assert(std::is_nothrow_convertible_v, int>); +static_assert(!std::is_nothrow_convertible_v, int>); + +struct NonConvertibleToAnything {}; + +namespace detail { + template + void check_implicit_conversion(T); // not defined +} + +template +concept NotImplicitlyConstructibleFrom = std::constructible_from && !requires(Args&&... args) { + detail::check_implicit_conversion({std::forward(args)...}); +}; + +namespace detail { + template + inline constexpr bool is_extents_v = false; + + template + inline constexpr bool is_extents_v> = true; + + template + inline constexpr bool is_mapping_of_v = + std::is_same_v, Mapping>; + + template + concept CheckNestedTypesOfLayoutMapping = is_extents_v + && std::same_as + && std::same_as + && is_mapping_of_v; + + 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; + }; + + template + concept CheckStaticFunctionsOfLayoutMapping = requires { + { M::is_always_strided() } -> std::same_as; + { M::is_always_exhaustive() } -> std::same_as; + { M::is_always_unique() } -> std::same_as; + std::bool_constant::value; + std::bool_constant::value; + std::bool_constant::value; + }; +} // namespace detail + +template +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) { + { mapping.stride(i) } -> std::same_as; +}; + +template +constexpr bool check_layout_mapping_requirements() { + static_assert(std::copyable); + static_assert(std::equality_comparable); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_swappable_v); + static_assert(detail::CheckNestedTypesOfLayoutMapping); + static_assert(detail::CheckMemberFunctionsOfLayoutMapping); + static_assert(detail::CheckStaticFunctionsOfLayoutMapping); + + [](std::index_sequence) { + static_assert(CheckCallOperatorOfLayoutMapping); + }(std::make_index_sequence{}); + + if constexpr (requires(M m, M::rank_type i) { m.stride(i); }) { + static_assert(CheckStrideMemberFunction); + } + + return true; +} + +template + requires detail::is_extents_v +constexpr bool check_layout_mapping_policy_requirements() { + using X = MP::template mapping; + static_assert(check_layout_mapping_requirements()); + static_assert(std::same_as); + static_assert(std::same_as); + return true; +} + +template +constexpr bool check_accessor_policy_requirements(); + +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 + && std::is_nothrow_swappable_v + && std::common_reference_with + && (std::same_as + || check_accessor_policy_requirements()) + && std::constructible_from + && std::is_same_v; + + 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; + }; +} // namespace detail + +template +constexpr bool check_accessor_policy_requirements() { + 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); + static_assert(detail::CheckNestedTypesOfAccessorPolicy); + static_assert(detail::CheckMemberFunctionsOfAccessorPolicy); + return true; +} + +namespace details { + template + constexpr void check_members_with_mixed_extents(Fn&& fn) { + auto select_extent = [](size_t e) consteval { + return e == std::dynamic_extent ? (std::min)(sizeof...(Extents), size_t{3}) : e; + }; + + // Check signed integers + fn(std::extents{select_extent(Extents)...}); + fn(std::extents{select_extent(Extents)...}); + fn(std::extents{select_extent(Extents)...}); + fn(std::extents{select_extent(Extents)...}); + fn(std::extents{select_extent(Extents)...}); + + // Check unsigned integers + fn(std::extents{select_extent(Extents)...}); + fn(std::extents{select_extent(Extents)...}); + fn(std::extents{select_extent(Extents)...}); + fn(std::extents{select_extent(Extents)...}); + fn(std::extents{select_extent(Extents)...}); + } + + template + constexpr void check_members_with_various_extents_impl(Fn&& fn, std::index_sequence) { + auto static_or_dynamic = [](size_t i) consteval { + return i == 0 ? std::dynamic_extent : (std::min)(sizeof...(Seq), size_t{3}); + }; + + if constexpr (sizeof...(Seq) <= 1) { + check_members_with_mixed_extents<>(std::forward(fn)); + } else if constexpr (sizeof...(Seq) <= 2) { + (check_members_with_mixed_extents(std::forward(fn)), ...); + } else if constexpr (sizeof...(Seq) <= 4) { + (check_members_with_mixed_extents( + std::forward(fn)), + ...); + } else if constexpr (sizeof...(Seq) <= 8) { + (check_members_with_mixed_extents(std::forward(fn)), + ...); + } else if constexpr (sizeof...(Seq) <= 16) { + (check_members_with_mixed_extents(std::forward(fn)), + ...); + } else { + static_assert(sizeof...(Seq) <= 16, "We don't need more testing."); + } + } +} // namespace details + +template +constexpr void check_members_with_various_extents(Fn&& fn) { + details::check_members_with_various_extents_impl(std::forward(fn), std::make_index_sequence<1>{}); + details::check_members_with_various_extents_impl(std::forward(fn), std::make_index_sequence<2>{}); + details::check_members_with_various_extents_impl(std::forward(fn), std::make_index_sequence<4>{}); + details::check_members_with_various_extents_impl(std::forward(fn), std::make_index_sequence<8>{}); + if (!std::is_constant_evaluated()) { + details::check_members_with_various_extents_impl(std::forward(fn), std::make_index_sequence<16>{}); + } +} + +namespace details { + static constexpr bool permissive() { + return false; + } + + template + struct PermissiveTestBase { + static constexpr bool permissive() { + return true; + } + }; + + template + struct PermissiveTest : PermissiveTestBase { + static constexpr bool test() { + return permissive(); + } + }; +} // namespace details + +inline constexpr bool is_permissive = details::PermissiveTest::test(); + +template +struct MappingProperties { + typename Mapping::index_type req_span_size; + bool uniqueness; + bool exhaustiveness; + bool strideness; +}; + +template + requires (!details::PermissiveTest::test()) +MappingProperties get_mapping_properties(const Mapping& mapping) { + using IndexType = typename Mapping::index_type; + constexpr auto rank = Mapping::extents_type::rank(); + constexpr std::make_index_sequence rank_indices; + + auto get_extent = [&](size_t i) { return mapping.extents().extent(i); }; + auto multidim_indices = [&](std::index_sequence) { + return std::views::cartesian_product(std::views::iota(IndexType{0}, get_extent(Indices))...); + }(rank_indices); + + auto map_index = [&](const auto& tpl) { return std::apply([&](auto... i) { return mapping(i...); }, tpl); }; + auto mapped_indices = multidim_indices | std::views::transform(map_index) | std::ranges::to(); + std::ranges::sort(mapped_indices); + + MappingProperties props{}; + + // Find required span size (N4950 [mdspan.layout.reqmts]/12) + if (std::ranges::contains(std::views::iota(0u, rank) | std::views::transform(get_extent), IndexType{0})) { + props.req_span_size = 0; + } else { + props.req_span_size = static_cast(1 + mapped_indices.back()); + } + + // Is mapping unique? (N4950 [mdspan.layout.reqmts]/14) + props.uniqueness = std::ranges::adjacent_find(mapped_indices) == mapped_indices.end(); + + // Is mapping exhaustive? (N4950 [mdspan.layout.reqmts]/16) + props.exhaustiveness = + std::ranges::adjacent_find(mapped_indices, [](auto x, auto y) { return y - x > 1; }) == mapped_indices.end(); + + { // Is mapping strided? (N4950 [mdspan.layout.reqmts]/18) + props.strideness = true; // assumption + for (auto r : std::views::iota(0u, rank)) { + std::optional sr; + for (auto i : multidim_indices) { + const auto i_plus_dr = [&](std::index_sequence) { + return std::array{ + static_cast(std::get(i) + (Indices == r ? 1 : 0))...}; + }(rank_indices); + + if (i_plus_dr[r] < get_extent(r)) { + const auto diff = static_cast(map_index(i_plus_dr) - map_index(i)); + if (!sr.has_value()) { + sr = diff; + } else if (*sr != diff) { + props.strideness = false; + break; + } + } + } + + if (!props.strideness) { + break; + } + } + } + + return props; +} + +template + requires (details::PermissiveTest::test()) +constexpr MappingProperties get_mapping_properties(const Mapping&) { + return {}; // we cannot get properties in '/permissive' mode +} diff --git a/tests/std/test.lst b/tests/std/test.lst index c2190304dcc..e493dcb6540 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -241,6 +241,17 @@ tests\LWG3422_seed_seq_ctors tests\LWG3480_directory_iterator_range tests\LWG3545_pointer_traits_sfinae tests\LWG3610_iota_view_size_and_integer_class +tests\P0009R18_mdspan_default_accessor +tests\P0009R18_mdspan_extents +tests\P0009R18_mdspan_extents_death +tests\P0009R18_mdspan_layout_left +tests\P0009R18_mdspan_layout_left_death +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\P0009R18_mdspan_mdspan_death tests\P0019R8_atomic_ref tests\P0024R2_parallel_algorithms_adjacent_difference tests\P0024R2_parallel_algorithms_adjacent_find diff --git a/tests/std/tests/GH_002206_unreserved_names/test.compile.pass.cpp b/tests/std/tests/GH_002206_unreserved_names/test.compile.pass.cpp index d5534e22344..c57279e236c 100644 --- a/tests/std/tests/GH_002206_unreserved_names/test.compile.pass.cpp +++ b/tests/std/tests/GH_002206_unreserved_names/test.compile.pass.cpp @@ -13,6 +13,7 @@ #define intrinsic 3 #define lifetimebound 4 #define noop_dtor 5 +#define empty_bases 6 #include <__msvc_all_public_headers.hpp> @@ -35,3 +36,7 @@ #if noop_dtor != 5 #error bad macro expansion #endif // noop_dtor != 5 + +#if empty_bases != 6 +#error bad macro expansion +#endif // empty_bases != 6 diff --git a/tests/std/tests/P0009R18_mdspan_default_accessor/env.lst b/tests/std/tests/P0009R18_mdspan_default_accessor/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_default_accessor/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_default_accessor/test.cpp b/tests/std/tests/P0009R18_mdspan_default_accessor/test.cpp new file mode 100644 index 00000000000..aa0e829edb2 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_default_accessor/test.cpp @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +template +constexpr void test_one(array elems) { + using Accessor = default_accessor; + + // default_accessor meets the accessor policy requirements + static_assert(check_accessor_policy_requirements()); + + // Check modeled concepts + 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); + static_assert(semiregular); + + // Check if default_accessor is empty + static_assert(is_empty_v); + + // Check nested types + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + + // Check default constructor + Accessor accessor; + static_assert(is_nothrow_default_constructible_v); + + { // Check converting constructor from other accessor + [[maybe_unused]] default_accessor const_accessor = accessor; + static_assert(is_nothrow_constructible_v, Accessor>); + static_assert(!is_constructible_v>); + } + + { // Check 'access' member function + same_as decltype(auto) accessed_elem = accessor.access(elems.data(), 1); + assert(accessed_elem == elems[1]); + static_assert(noexcept(accessor.access(elems.data(), 0))); + } + + { // Check 'offset' member function + same_as auto ptr = accessor.offset(elems.data(), 1); + assert(ptr == elems.data() + 1); + static_assert(noexcept(accessor.offset(elems.data(), 0))); + } +} + +constexpr bool test() { + test_one({'a', 'b', 'c'}); + test_one({1, 2, 3}); + test_one({1.1, 2.2, 3.3}); + test_one({L"1", L"2", L"3"}); + test_one({3, 2, 1}); + return true; +} + +int main() { + static_assert(test()); + test(); +} diff --git a/tests/std/tests/P0009R18_mdspan_extents/env.lst b/tests/std/tests/P0009R18_mdspan_extents/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_extents/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_extents/test.cpp b/tests/std/tests/P0009R18_mdspan_extents/test.cpp new file mode 100644 index 00000000000..2dedead9cc9 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_extents/test.cpp @@ -0,0 +1,557 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +template +constexpr void check_members(index_sequence) { + using Ext = extents; + + // Each specialization of extents models regular and is trivially copyable + static_assert(regular); + static_assert(is_trivially_copyable_v); + + // Check implicit properties + static_assert(is_nothrow_copy_constructible_v); + static_assert(is_nothrow_move_constructible_v); + static_assert(is_nothrow_copy_assignable_v); + static_assert(is_nothrow_move_assignable_v); + static_assert(is_nothrow_swappable_v); + + // Check member types + static_assert(same_as); + static_assert(same_as>); + static_assert(same_as); + + // Check static observers + static_assert(Ext::rank() == sizeof...(Extents)); + static_assert(Ext::rank_dynamic() == (size_t{Extents == dynamic_extent} + ... + 0)); + static_assert(((Ext::static_extent(Indices) == Extents) && ...)); + + // Check noexceptness of static observers + static_assert(noexcept(Ext::rank())); + static_assert(noexcept(Ext::rank_dynamic())); + static_assert(noexcept(Ext::static_extent(0))); + + // Check default constructor + Ext ext; + static_assert(is_nothrow_default_constructible_v); + + // Check 'extent' observer + assert( + (((cmp_equal(ext.extent(Indices), Extents) && Extents != dynamic_extent) || ext.extent(Indices) == 0) && ...)); + + using OtherIndexType = conditional_t, long long, unsigned long long>; + using Ext2 = extents; + + { // Check construction from other extents + Ext2 ext2{ext}; + assert(((ext.extent(Indices) == ext2.extent(Indices)) && ...)); + assert(ext == ext2); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_other_extents' function + } + + { // Check construction from extents pack + Ext2 ext2{ext.extent(Indices)...}; + assert(((ext.extent(Indices) == ext2.extent(Indices)) && ...)); + assert(ext == ext2); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_extents_pack' function + } + + { // Check construction from array and span + array arr = {ext.extent(Indices)...}; + Ext2 ext2a{arr}; + assert(((ext.extent(Indices) == ext2a.extent(Indices)) && ...)); + assert(ext == ext2a); + static_assert(is_nothrow_constructible_v); + + span s{arr}; + Ext2 ext2b{s}; + assert(((ext.extent(Indices) == ext2b.extent(Indices)) && ...)); + assert(ext == ext2b); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_array_and_span' function + } +} + +constexpr void check_defaulted_default_constructor() { + { // All extents are static + using Ext = extents; + static_assert(is_nothrow_default_constructible_v); + + Ext ext; + assert(ext.extent(0) == 3); + assert(ext.extent(1) == 5); + assert(ext.extent(2) == 7); + } + + { // Some extents are static, some dynamic + using Ext = extents; + static_assert(is_nothrow_default_constructible_v); + + Ext ext; + assert(ext.extent(0) == 0); + assert(ext.extent(1) == 0); + assert(ext.extent(2) == 4); + } + + { // All extents are dynamic + using Ext = dextents; + static_assert(is_nothrow_default_constructible_v); + + Ext ext; + assert(ext.extent(0) == 0); + assert(ext.extent(1) == 0); + assert(ext.extent(2) == 0); + } +} + +constexpr void check_construction_from_other_extents() { + { // Check construction from too big or too small other extents + using Ext = extents; + static_assert(!is_constructible_v>); + static_assert(!is_constructible_v>); + } + + { // Check construction with different values + static_assert(is_nothrow_constructible_v, extents>); + static_assert(is_nothrow_constructible_v, extents>); + static_assert(is_nothrow_constructible_v, extents>); + static_assert(is_nothrow_constructible_v, extents>); + static_assert(!is_constructible_v, extents>); + } + + { // Check postconditions: static extents from static extents + extents ext; + extents ext2 = ext; + assert(ext2.extent(0) == 3); + assert(ext2.extent(1) == 4); + assert(ext == ext2); + } + + { // Check postconditions: dynamic extents from static extents + extents ext; + dextents ext2 = ext; + assert(ext2.extent(0) == 2); + assert(ext2.extent(1) == 3); + assert(ext == ext2); + } + + { // Check postconditions: dynamic extents from dynamic extents + dextents ext{5, 10}; + dextents ext2 = ext; + assert(ext2.extent(0) == 5); + assert(ext2.extent(1) == 10); + assert(ext == ext2); + } + + { // Check postconditions: static extents from dynamic extents + dextents ext{9, 6}; + extents ext2{ext}; // NB: explicit constructor + assert(ext2.extent(0) == 9); + assert(ext2.extent(1) == 6); + assert(ext == ext2); + } + + { // Check postconditions: wider index type to narrower index type + dextents ext{3, 5}; + dextents ext2{ext}; // NB: explicit constructor + assert(ext2.extent(0) == 3); + assert(ext2.extent(1) == 5); + assert(ext == ext2); + } + + { // Check implicit conversions + static_assert(!NotImplicitlyConstructibleFrom, extents>); + static_assert(NotImplicitlyConstructibleFrom, extents>); + static_assert(NotImplicitlyConstructibleFrom, extents>); + static_assert(NotImplicitlyConstructibleFrom, extents>); + } +} + +constexpr void check_construction_from_extents_pack() { + { // Check construction from various types + using Ext = extents; + static_assert(is_nothrow_constructible_v); + static_assert(!is_constructible_v); + static_assert(is_nothrow_constructible_v); + } + + { // Check construction from types (not) convertible to index_type + using Ext = extents; + static_assert(is_nothrow_constructible_v>); + static_assert(!is_nothrow_constructible_v>); + static_assert(!is_constructible_v); + static_assert(is_nothrow_constructible_v>); + static_assert(!is_nothrow_constructible_v>); + static_assert(!is_constructible_v); + } + + { // Check construction from types that may throw during conversion to index_type + using Ext = extents; + static_assert(!is_constructible_v>); + static_assert(!is_constructible_v>); + } + + { // Check postconditions when 'sizeof...(pack) == rank()' + using Ext = extents; + Ext ext{4, ConvertibleToInt{.val = 4}, 4}; + assert(ext.extent(0) == 4); + assert(ext.extent(1) == 4); + assert(ext.extent(2) == 4); + Ext ext2{4, 4, 4}; + assert(ext == ext2); + } + + { // Check postconditions when 'sizeof...(pack) == rank_dynamic()' + using Ext = extents; + Ext ext{3, ConvertibleToInt{.val = 3}}; + assert(ext.extent(0) == 3); + assert(ext.extent(1) == 3); + assert(ext.extent(2) == 5); + Ext ext2{3, 3}; + assert(ext == ext2); + } + + { // Check that elements from pack are passed through 'move' + using Ext = dextents; + struct FancyIndex { + constexpr operator integral auto() const& noexcept { + return 3; + } + + constexpr operator integral auto() const&& noexcept { + return 3; + } + + constexpr operator Ext::index_type() && noexcept { + return 4; + } + }; + + FancyIndex i; + Ext ext{FancyIndex{}, i, as_const(i)}; + assert(ext.extent(0) == 4); + assert(ext.extent(1) == 4); + assert(ext.extent(2) == 4); + } + + { // Check construction from integers with mismatched signs + using Ext = extents; + (void) Ext{4ull}; + } + + { // Check narrowing conversions + using Ext = extents; + (void) Ext{4ll}; + } + + { // Check implicit conversions + static_assert(NotImplicitlyConstructibleFrom, unsigned long long>); + static_assert(NotImplicitlyConstructibleFrom, long, long>); + static_assert(NotImplicitlyConstructibleFrom, char, signed char, unsigned char>); + } +} + +constexpr void check_construction_from_array_and_span() { + { // Check construction from array/span where 'size()' is equal to 'rank()' and OtherIndexType models 'integral' + using Ext = extents; + array arr = {1, 5}; + Ext ext{arr}; + assert(ext.extent(0) == 1); + assert(ext.extent(1) == 5); + + Ext ext2{span{arr}}; + assert(ext == ext2); + + static_assert(is_nothrow_constructible_v); + static_assert(is_nothrow_constructible_v>); + } + + { // Check construction from array/span where 'size()' is equal to 'rank()' and OtherIndexType is class type + using Ext = extents; + array, 2> arr{{{.val = 3}, {.val = 5}}}; + Ext ext{arr}; + assert(ext.extent(0) == 3); + assert(ext.extent(1) == 5); + + const span s{arr}; + Ext ext2{s}; + assert(ext == ext2); + + static_assert(is_nothrow_constructible_v); + static_assert(is_nothrow_constructible_v); + } + + { // Check construction from array/span where 'size()' is equal to 'rank()' and OtherIndexType is "special" + using Ext = extents; + struct SpecialIndex { + constexpr operator integral auto() noexcept { + return 3; + } + + constexpr operator integral auto() const noexcept { + return 5; + } + }; + + // Elements of 'arr' and 's' should be passed through 'as_const' + array arr; + Ext ext{arr}; + assert(ext.extent(0) == 5); + assert(ext.extent(1) == 5); + + span s{arr}; + Ext ext2{s}; + assert(ext == ext2); + } + + { // Check invalid construction from array/span where 'size()' is equal to 'rank()' + using Ext = extents; + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v>); + static_assert(!is_constructible_v>); + } + + { // Check construction from array/span where 'size()' is equal to 'rank_dynamic()' and OtherIndexType models + // 'integral' + using Ext = extents; + array arr = {4, 4}; + Ext ext{arr}; + assert(ext.extent(0) == 3); + assert(ext.extent(1) == 4); + assert(ext.extent(2) == 3); + assert(ext.extent(3) == 4); + + Ext ext2{span{arr}}; + assert(ext == ext2); + + static_assert(is_nothrow_constructible_v); + static_assert(is_nothrow_constructible_v>); + } + + { // Check construction from array/span where 'size()' is equal to 'rank_dynamic()' and OtherIndexType is class type + using Ext = extents; + array, 2> arr{{{.val = 2}, {.val = 2}}}; + Ext ext{arr}; + assert(ext.extent(0) == 3); + assert(ext.extent(1) == 2); + assert(ext.extent(2) == 3); + assert(ext.extent(3) == 2); + + span s{arr}; + Ext ext2{s}; + assert(ext == ext2); + + static_assert(is_nothrow_constructible_v); + static_assert(is_nothrow_constructible_v); + } + + { // Check construction from array/span where 'size()' is equal to 'rank_dynamic()' and OtherIndexType is "special" + using Ext = extents; + struct SpecialIndex { + constexpr operator integral auto() noexcept { + return 5; + } + + constexpr operator integral auto() const noexcept { + return 3; + } + }; + + // Elements of 'arr' and 's' should be passed through 'as_const' + array arr; + Ext ext{arr}; + assert(ext.extent(0) == 3); + assert(ext.extent(1) == 3); + assert(ext.extent(2) == 3); + + span s{arr}; + Ext ext2{s}; + assert(ext == ext2); + } + + { // Check invalid construction from array/span where 'size()' is equal to 'rank_dynamic()' + using Ext = extents; + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v, 2>>); + static_assert(!is_constructible_v>); + static_assert(!is_constructible_v>); + } + + { // Check construction with integers with mismatched signs + using Ext = extents; + + array arr = {4ull}; + (void) Ext{arr}; + + span s{arr}; + (void) Ext{s}; + } + + { // Check narrowing conversions + using Ext = extents; + + array arr = {4ll}; + (void) Ext{arr}; + (void) Ext{span{arr}}; + } + + { // Check construction from arrays/spans with invalid size + using Ext = extents; + 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 implicit conversions + static_assert(!NotImplicitlyConstructibleFrom, array>); + static_assert(NotImplicitlyConstructibleFrom, array>); + static_assert(!NotImplicitlyConstructibleFrom, span>); + static_assert(NotImplicitlyConstructibleFrom, span>); + } +} + +constexpr void check_equality_operator() { + { // All extents are static + extents e1; + extents e2; + extents e3; + + same_as decltype(auto) cond = e1 != e2; + assert(cond); + assert(e2 != e3); + assert(e1 == e3); + + static_assert(noexcept(e1 != e2)); + static_assert(noexcept(e1 == e3)); + } + + { // Some extents are static, some dynamic + extents e1{1}; + extents e2{2}; + extents e3{3}; + + same_as decltype(auto) cond = e1 != e2; + assert(cond); + assert(e2 == e3); + assert(e1 != e2); + + static_assert(noexcept(e1 != e2)); + static_assert(noexcept(e2 == e3)); + } + + { // All extents are dynamic + dextents e1{1, 2}; + dextents e2{1, 2}; + dextents e3{1, 3}; + + same_as decltype(auto) cond = e1 == e2; + assert(cond); + assert(e2 != e3); + assert(e1 != e3); + + static_assert(noexcept(e1 == e2)); + static_assert(noexcept(e2 != e3)); + } + + { // Different ranks + static_assert(extents{} != extents{}); + static_assert(extents{} != extents{}); + static_assert(noexcept(extents{} != extents{})); + static_assert(noexcept(extents{} != extents{})); + } +} + +template +concept CanDeduceExtents = requires(Args&&... args) { extents{forward(args)...}; }; + +template +constexpr bool all_extents_dynamic = false; + +template +constexpr bool all_extents_dynamic, ExpectedRank> = + ((Extents == dynamic_extent) && ...) && (sizeof...(Extents) == ExpectedRank); + +constexpr void check_deduction_guide() { + { // Check 'CanDeduceExtents' concept + static_assert(CanDeduceExtents); + static_assert(CanDeduceExtents); + static_assert(CanDeduceExtents, int>); + static_assert(!CanDeduceExtents, int>); + static_assert(!CanDeduceExtents); + } + + { // Check correctness + extents ext{'1', 2, 3u, 4ll, ConvertibleToInt{.val = 5}}; + assert(ext.extent(0) == '1'); + assert(ext.extent(1) == 2); + assert(ext.extent(2) == 3); + assert(ext.extent(3) == 4); + assert(ext.extent(4) == 5); + + static_assert(all_extents_dynamic); + static_assert(same_as); + } +} + +constexpr bool test() { + check_members_with_various_extents([](const extents&) { + check_members(make_index_sequence{}); + }); + check_defaulted_default_constructor(); + check_construction_from_other_extents(); + check_construction_from_extents_pack(); + check_construction_from_array_and_span(); + check_equality_operator(); + check_deduction_guide(); + + return true; +} + +// Check dextents +static_assert(all_extents_dynamic, 0>); +static_assert(all_extents_dynamic, 1>); +static_assert(all_extents_dynamic, 2>); +static_assert(all_extents_dynamic, 3>); +static_assert(all_extents_dynamic, 4>); +static_assert(all_extents_dynamic, 5>); +static_assert(all_extents_dynamic, 6>); +static_assert(all_extents_dynamic, 7>); +static_assert(all_extents_dynamic, 8>); +static_assert(all_extents_dynamic, 9>); + +// When 'E::rank_dynamic()' is equal to 0 then 'is_empty_v' should be true (MSVC STL specific behavior) +static_assert(!is_empty_v>); +static_assert(!is_empty_v>); +static_assert(is_empty_v>); +static_assert(is_empty_v>); + +int main() { + static_assert(test()); + test(); +} diff --git a/tests/std/tests/P0009R18_mdspan_extents_death/env.lst b/tests/std/tests/P0009R18_mdspan_extents_death/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_extents_death/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_extents_death/test.cpp b/tests/std/tests/P0009R18_mdspan_extents_death/test.cpp new file mode 100644 index 00000000000..6565c43abe7 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_extents_death/test.cpp @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include +#include +#include + +#include +#include + +using namespace std; + +void test_static_extent_function_with_invalid_index() { + using E = extents; + // Index must be less than rank() + (void) E::static_extent(1); +} + +void test_extent_function_with_invalid_index() { + extents e; + // Index must be less than rank() + (void) e.extent(1); +} + +void test_construction_from_other_extents_with_invalid_values() { + extents e1{1, 2}; + // Value of other.extent(r) must be equal to extent(r) for each r for which extent(r) is a static extent + [[maybe_unused]] extents e2{e1}; +} + +void test_construction_from_other_extents_with_unrepresentable_as_index_type_values() { + extents e1{256}; + // Value of other.extent(r) must be representable as a value of type index_type for every rank index r + [[maybe_unused]] extents e2{e1}; +} + +void test_construction_from_pack_with_invalid_values() { + // Value of exts_arr[r] must be equal to extent(r) for each r for which extent(r) is a static extent + [[maybe_unused]] extents e{1, 1}; +} + +void test_construction_from_pack_with_unrepresentable_as_index_type_values_1() { + // Either sizeof...(exts) must be equal to 0 or each element of exts must be nonnegative and must be representable + // as value of type index_type + [[maybe_unused]] extents e{1, 256}; +} + +void test_construction_from_pack_with_unrepresentable_as_index_type_values_2() { + // Either sizeof...(exts) must be equal to 0 or each element of exts must be nonnegative and must be representable + // as value of type index_type + [[maybe_unused]] extents e{ConvertibleToInt{.val = 1}, 256}; +} + +void test_construction_from_pack_with_unrepresentable_as_index_type_values_3() { + static_assert(signed_integral, "This test assumes that it isn't being compiled with /J"); + // Either sizeof...(exts) must be equal to 0 or each element of exts must be nonnegative and must be representable + // as value of type index_type + [[maybe_unused]] extents e{static_cast(-1)}; +} + +void test_construction_from_span_with_invalid_values() { + int vals[] = {1, 2}; + span s{vals}; + // Value of exts[r] must be equal to extent(r) for each r for which extent(r) is a static extent + [[maybe_unused]] extents e{s}; +} + +void test_construction_from_span_with_unrepresentable_as_index_type_values() { + int vals[] = {256}; + span s{vals}; + // Either N must be zero or exts[r] must be nonnegative and must be representable as value of type index_type for + // every rank index r + [[maybe_unused]] extents e{s}; +} + +void test_construction_from_array_with_invalid_values() { + array a = {1, 2}; + // Value of exts[r] must be equal to extent(r) for each r for which extent(r) is a static extent + [[maybe_unused]] extents e{a}; +} + +void test_construction_from_array_with_unrepresentable_as_index_type_values() { + array a = {256}; + // Either N must be zero or exts[r] must be nonnegative and must be representable as value of type index_type for + // every rank index r + [[maybe_unused]] extents e{a}; +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + exec.add_death_tests({ + test_static_extent_function_with_invalid_index, + test_extent_function_with_invalid_index, + test_construction_from_other_extents_with_invalid_values, + test_construction_from_other_extents_with_unrepresentable_as_index_type_values, + test_construction_from_pack_with_invalid_values, + test_construction_from_pack_with_unrepresentable_as_index_type_values_1, + test_construction_from_pack_with_unrepresentable_as_index_type_values_2, + test_construction_from_pack_with_unrepresentable_as_index_type_values_3, + test_construction_from_span_with_invalid_values, + test_construction_from_span_with_unrepresentable_as_index_type_values, + test_construction_from_array_with_invalid_values, + test_construction_from_array_with_unrepresentable_as_index_type_values, + }); + return exec.run(argc, argv); +} diff --git a/tests/std/tests/P0009R18_mdspan_layout_left/env.lst b/tests/std/tests/P0009R18_mdspan_layout_left/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_left/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_layout_left/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_left/test.cpp new file mode 100644 index 00000000000..3d3d511a9e6 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_left/test.cpp @@ -0,0 +1,518 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +template +constexpr void check_members(const extents& ext, index_sequence) { + using Ext = extents; + using Mapping = layout_left::mapping; + + // layout_left meets the layout mapping policy requirements and is a trivial type + static_assert(check_layout_mapping_policy_requirements()); + static_assert(is_trivial_v); + + // layout_left::mapping is a trivially copyable type that models regular for each Ext + static_assert(is_trivially_copyable_v); + static_assert(regular); + + // Check member types + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + + { // Check default and copy constructor + const Mapping m; + Mapping cpy = m; + assert(cpy == m); + static_assert(is_nothrow_default_constructible_v); + static_assert(is_nothrow_copy_constructible_v); + } + + { // Check copy assignment operator + const Mapping m; + Mapping cpy; + cpy = m; + assert(cpy == m); + static_assert(is_nothrow_copy_assignable_v); + } + + { // Check construction from extents_type + Mapping m{ext}; + assert(m.extents() == ext); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_extents' function + } + + using OtherIndexType = long long; + using Ext2 = extents; + using Mapping2 = layout_left::mapping; + + { // Check construction from other layout_left::mapping + Mapping m1{ext}; + Mapping2 m2{m1}; + assert(m1 == m2); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_other_left_mapping' function + } + + { // Check construction from layout_right::mapping + using RightMapping = layout_right::mapping; + if constexpr (Ext::rank() <= 1) { + RightMapping right_mapping{ext}; + [[maybe_unused]] Mapping m1{right_mapping}; + [[maybe_unused]] Mapping2 m2{right_mapping}; + assert(m1 == m2); + static_assert(is_nothrow_constructible_v); + static_assert(is_nothrow_constructible_v); + } else { + static_assert(!is_constructible_v); + static_assert(!is_constructible_v); + } + // Other tests are defined in 'check_construction_from_other_right_mapping' function + } + + { // Check construction from layout_stride::mapping + array strides{}; + if constexpr (Ext::rank() > 0) { + strides.front() = 1; + for (size_t i = 1; i < Ext::rank(); ++i) { +#pragma warning(push) +#pragma warning(disable : 28020) // TRANSITION, DevCom-923103 + strides[i] = static_cast(strides[i - 1] * ext.extent(i - 1)); +#pragma warning(pop) + } + } + + layout_stride::mapping m1{ext, strides}; + Mapping m2{m1}; + assert(m1.extents() == m2.extents()); + // Other tests are defined in 'check_construction_from_other_stride_mapping' function + } + + Mapping m{ext}; // For later use + + { // Check 'extents' function + assert(m.extents() == ext); + static_assert(noexcept(m.extents())); + } + + { // Check 'required_span_size' function + same_as decltype(auto) rss = m.required_span_size(); + assert(rss == static_cast((ext.extent(Indices) * ... * 1))); + static_assert(noexcept(m.required_span_size())); + // Other tests are defined in 'check_mapping_properties' + } + + { // Check operator() + assert(m(((void) Indices, 0)...) == 0); + assert(m((ext.extent(Indices) - 1)...) == static_cast((ext.extent(Indices) * ... * 1)) - 1); + static_assert(noexcept(m(((void) Indices, 0)...))); + static_assert(noexcept(m((ext.extent(Indices) - 1)...))); + // Other tests are defined in 'check_call_operator' function + } + + { // Check 'is_always_[unique/exhaustive/strided]' functions + static_assert(Mapping::is_always_unique()); + static_assert(Mapping::is_always_exhaustive()); + static_assert(Mapping::is_always_strided()); + } + + { // Check 'is_[unique/exhaustive/strided]' functions + static_assert(Mapping::is_unique()); + static_assert(Mapping::is_exhaustive()); + static_assert(Mapping::is_strided()); + // Other tests are defined in 'check_mapping_properties' + } + + if constexpr (Ext::rank() > 0) { // Check 'stride' function + const IndexType expected_value = + static_cast((ext.extent(Indices) * ... * 1) / ext.extent(Ext::rank() - 1)); + assert(m.stride(Ext::rank() - 1) == expected_value); + assert(m.stride(0) == 1); + static_assert(noexcept(m.stride(Ext::rank() - 1))); + static_assert(noexcept(m.stride(0))); + // Other tests are defined in 'check_stride_function' + } else { + static_assert(!CheckStrideMemberFunction); + } + + { // Check comparisons + assert(m == m); + assert(!(m != m)); + static_assert(noexcept(m == m)); + static_assert(noexcept(m != m)); + // Other tests are defined in 'check_comparisons' function + } +} + +void check_mapping_properties() { + if constexpr (!is_permissive) { + auto check = [](const auto& mapping) { + const auto props = get_mapping_properties(mapping); + assert(props.req_span_size == mapping.required_span_size()); + assert(props.uniqueness); + assert(props.exhaustiveness); + assert(props.strideness); + }; + + using M1 = layout_left::mapping>; + check(M1{}); + + using M2 = layout_left::mapping>; + check(M2{M2::extents_type{6}}); + + using M3 = layout_left::mapping>; + check(M3{M3::extents_type{3, 5, 4, 2}}); + } +} + +constexpr void check_construction_from_extents() { + using Ext = extents; + Ext ext; + + { // Check construction from 'extents_type' + layout_left::mapping mp(ext); + assert(mp.extents() == ext); + static_assert(is_nothrow_constructible_v); + } + + { // Check construction from other extents + layout_left::mapping> mp(ext); + assert(mp.extents() == ext); + static_assert(is_nothrow_constructible_v); + } +} + +constexpr void check_construction_from_other_left_mapping() { + { // Check invalid construction + using Mapping = layout_left::mapping>; + static_assert(!is_constructible_v>>); + static_assert(!is_constructible_v>>); + } + + { // Check implicit conversions + static_assert(!NotImplicitlyConstructibleFrom>, + layout_left::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_left::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_left::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_left::mapping>>); + } + + { // Check effects + layout_left::mapping> m1; + layout_left::mapping> m2{m1}; + assert(m2.extents().extent(0) == 5); + assert(m2.extents().extent(1) == 3); + assert(m2.extents().extent(2) == 5); + assert(m1.extents() == m2.extents()); + } +} + +constexpr void check_construction_from_other_right_mapping() { + { // Check construction from layout_right::mapping with various values of E::rank() + static_assert(is_nothrow_constructible_v>, + layout_right::mapping>>); + static_assert(is_nothrow_constructible_v>, + layout_right::mapping>>); + static_assert( + !is_constructible_v>, layout_right::mapping>>); + static_assert( + !is_constructible_v>, layout_right::mapping>>); + } + + { // Check construction from layout_right::mapping when E is invalid + using Mapping = layout_left::mapping>; + static_assert(!is_constructible_v>>); + static_assert(!is_constructible_v>>); + } + + { // Check implicit conversions + using Mapping = layout_left::mapping>; + static_assert(!NotImplicitlyConstructibleFrom>>); + static_assert(NotImplicitlyConstructibleFrom>>); + static_assert(NotImplicitlyConstructibleFrom>>); + } + + { // Check effects + layout_right::mapping> m1; + layout_left::mapping> m2{m1}; + assert(m2.extents().extent(0) == 8); + } +} + +constexpr void check_construction_from_other_stride_mapping() { + { // Check construction from layout_stride::mapping with various values of E::rank() + static_assert(is_nothrow_constructible_v>, + layout_stride::mapping>>); // strengthened + static_assert(is_nothrow_constructible_v>, + layout_stride::mapping>>); // strengthened + static_assert(is_nothrow_constructible_v>, + layout_stride::mapping>>); // strengthened + static_assert(is_nothrow_constructible_v>, + layout_stride::mapping>>); // strengthened + } + + { // Check construction from layout_stride::mapping when E is invalid + using Mapping = layout_left::mapping>; + static_assert(!is_constructible_v>>); + static_assert(!is_constructible_v>>); + } + + { // Check correctness + using Ext = extents; + layout_stride::mapping m1{Ext{}, array{1, 4, 12, 24, 72}}; + layout_left::mapping m2{m1}; + assert(m2.extents().extent(0) == 4); + assert(m2.extents().extent(1) == 3); + assert(m2.stride(2) == 12); + assert(m2.stride(3) == 24); + assert(m2.extents() == m1.extents()); + } + + { // Check implicit conversions + static_assert( + !NotImplicitlyConstructibleFrom>, layout_stride::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_stride::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_stride::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_stride::mapping>>); + } +} + +constexpr void check_call_operator() { + { // Check call with invalid amount of indices + using Mapping = layout_left::mapping>; + static_assert(!CheckCallOperatorOfLayoutMapping); + static_assert(!CheckCallOperatorOfLayoutMapping); + static_assert(CheckCallOperatorOfLayoutMapping); + static_assert(!CheckCallOperatorOfLayoutMapping); + } + + { // Check call with invalid types + using Mapping = layout_left::mapping>; + static_assert(CheckCallOperatorOfLayoutMapping); + static_assert(CheckCallOperatorOfLayoutMapping); + static_assert(CheckCallOperatorOfLayoutMapping>); + static_assert(CheckCallOperatorOfLayoutMapping>); + static_assert(!CheckCallOperatorOfLayoutMapping); + } + + { // Check call with types that might throw during conversion + using Mapping = layout_left::mapping>; + static_assert(CheckCallOperatorOfLayoutMapping>); + static_assert(!CheckCallOperatorOfLayoutMapping>); + } + + { // Check various mappings + layout_left::mapping> m1; + assert(m1() == 0); + + layout_left::mapping> m2; + assert(m2(0) == 0); + assert(m2(1) == 1); + assert(m2(2) == 2); + + layout_left::mapping> m3{dextents{5, 6}}; + assert(m3(0, 0) == 0); + assert(m3(1, 0) == 1); + assert(m3(0, 1) == 5); + assert(m3(1, 1) == 6); + assert(m3(2, 1) == 7); + assert(m3(1, 2) == 11); + assert(m3(4, 5) == 29); + } +} + +constexpr void check_stride_function() { + layout_left::mapping> prime_mapping; + + { // Check return type + same_as decltype(auto) s = prime_mapping.stride(0); + assert(s == 1); + } + + { // Check that argument is 'rank_type' + struct ConvertibleToRankType { + constexpr operator integral auto() const { + return 0; + } + + constexpr operator size_t() const { // NB: 'rank_type' is always 'size_t' + return 1; + } + }; + + assert(prime_mapping.stride(ConvertibleToRankType{}) == 2); // 1 * 2 + } + + { // Check other strides + assert(prime_mapping.stride(2) == 6); + assert(prime_mapping.stride(3) == 30); + assert(prime_mapping.stride(4) == 210); + } +} + +constexpr void check_comparisons() { + using StaticMapping = layout_left::mapping>; + using DynamicMapping = layout_left::mapping>; + + { // Check equality_comparable_with concept + static_assert(equality_comparable_with); + static_assert(!equality_comparable_with>>); + static_assert(!equality_comparable_with>>); + } + + { // Check correctness + StaticMapping m1; + DynamicMapping m2{dextents{3}}; + DynamicMapping m3{dextents{2}}; + assert(m1 == m2); + assert(m2 != m3); + assert(m1 != m3); + } +} + +constexpr void check_ctad() { + using Ext = extents; + layout_left::mapping m{Ext{}}; + static_assert(same_as>); + assert(m.extents().extent(0) == 3); + assert(m.stride(1) == 3); +} + +constexpr void check_correctness() { + { // empty extents + const array values{}; + mdspan, layout_left> nothing{values.data()}; + assert(nothing.size() == 1); + } + + { // regular vector + const array values{0, 1, 2}; + mdspan, layout_left> vec{values.data()}; + +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 + assert(vec[0] == 0); + assert(vec[1] == 1); + assert(vec[2] == 2); +#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(__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 __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(__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(__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 __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(__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(__cpp_multidimensional_subscript) ^^^ + } + + { // 2x3x2x3 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, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; + mdspan, layout_left> tensor{values.data(), 2, 3}; + +#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)); + assert((tensor[0, 0, 0, 1] == 12)); + assert((tensor[0, 0, 0, 2] == 24)); + assert((tensor[0, 2, 0, 2] == 28)); + assert((tensor[1, 2, 1, 2] == 35)); +#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)); + assert((tensor[array{0, 0, 0, 1}] == 12)); + 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(__cpp_multidimensional_subscript) ^^^ + } +} + +// When 'M::extents_type::rank_dynamic()' is equal to 0 then 'is_empty_v' should be true (MSVC STL specific behavior) +static_assert(!is_empty_v>>); +static_assert(!is_empty_v>>); +static_assert(is_empty_v>>); +static_assert(is_empty_v>>); + +constexpr bool test() { + check_members_with_various_extents([](const E& e) { check_members(e, make_index_sequence{}); }); + if (!is_constant_evaluated()) { // too heavy for compile time + check_mapping_properties(); + } + check_construction_from_extents(); + check_construction_from_other_left_mapping(); + check_construction_from_other_right_mapping(); + check_construction_from_other_stride_mapping(); + check_call_operator(); + check_stride_function(); + check_comparisons(); + check_ctad(); + check_correctness(); + + return true; +} + +int main() { + static_assert(test()); + test(); +} diff --git a/tests/std/tests/P0009R18_mdspan_layout_left_death/env.lst b/tests/std/tests/P0009R18_mdspan_layout_left_death/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_left_death/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_layout_left_death/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_left_death/test.cpp new file mode 100644 index 00000000000..ae12da21424 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_left_death/test.cpp @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include +#include +#include + +#include + +using namespace std; + +void test_construction_from_extents_type_with_signed_index_type() { + using Ext = dextents; + // The size of the multidimensional index space e must be representable as a value of type index_type + [[maybe_unused]] layout_left::mapping m{Ext{5, 4, 7}}; +} + +void test_construction_from_extents_type_with_unsigned_index_type() { + using Ext = dextents; + // The size of the multidimensional index space e must be representable as a value of type index_type + [[maybe_unused]] layout_left::mapping m{Ext{5, 10, 6}}; +} + +void test_construction_from_other_left_mapping() { + layout_left::mapping> m1{dextents{256}}; + // Value of other.required_span_size() must be representable as a value of type index_type + layout_left::mapping> m2{m1}; +} + +void test_construction_from_other_right_mapping() { + layout_right::mapping> m1{dextents{256}}; + // Value of other.required_span_size() must be representable as a value of type index_type + layout_left::mapping> m2{m1}; +} + +void test_construction_from_other_stride_mapping_1() { + using Ext = extents; + layout_stride::mapping m1{Ext{}, array{4, 1}}; + // For all r in the range [0, extents_type::rank()), other.stride(r) must be equal to + // extents().fwd-prod-of-extents(r) + layout_left::mapping m2{m1}; +} + +void test_construction_from_other_stride_mapping_2() { + layout_stride::mapping> m1{dextents{256}, array{1}}; + // Value of other.required_span_size() must be representable as a value of type index_type + layout_left::mapping> m2{m1}; +} + +void test_call_operator() { + layout_left::mapping> m; + // Value of extents_type::index-cast(i) must be a multidimensional index in extents_ + (void) m(2, 3, 5); +} + +void test_stride_function() { + layout_left::mapping> m; + // Value of i must be less than extents_type::rank() + (void) m.stride(1); +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + exec.add_death_tests({ + test_construction_from_extents_type_with_signed_index_type, + test_construction_from_extents_type_with_unsigned_index_type, + test_construction_from_other_left_mapping, + test_construction_from_other_right_mapping, + test_construction_from_other_stride_mapping_1, + test_construction_from_other_stride_mapping_2, + test_call_operator, + test_stride_function, + }); + return exec.run(argc, argv); +} diff --git a/tests/std/tests/P0009R18_mdspan_layout_right/env.lst b/tests/std/tests/P0009R18_mdspan_layout_right/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_right/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_layout_right/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_right/test.cpp new file mode 100644 index 00000000000..c16cff03661 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_right/test.cpp @@ -0,0 +1,549 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +template +constexpr void check_members(const extents& ext, index_sequence) { + using Ext = extents; + using Mapping = layout_right::mapping; + + // layout_right meets the layout mapping policy requirements and is a trivial type + static_assert(check_layout_mapping_policy_requirements()); + static_assert(is_trivial_v); + + // layout_right::mapping is a trivially copyable type that models regular for each Ext + static_assert(is_trivially_copyable_v); + static_assert(regular); + + // Check member types + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + + { // Check default and copy constructor + const Mapping m; + Mapping cpy = m; + assert(cpy == m); + static_assert(is_nothrow_default_constructible_v); + static_assert(is_nothrow_copy_constructible_v); + } + + { // Check copy assignment operator + const Mapping m; + Mapping cpy; + cpy = m; + assert(cpy == m); + static_assert(is_nothrow_copy_assignable_v); + } + + { // Check construction from extents_type + Mapping m{ext}; + assert(m.extents() == ext); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_extents' function + } + + using OtherIndexType = long long; + using Ext2 = extents; + using Mapping2 = layout_right::mapping; + + { // Check construction from other layout_right::mapping + Mapping m1{ext}; + Mapping2 m2{m1}; + assert(m1 == m2); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_other_right_mapping' function + } + + { // Check construction from layout_left::mapping + using LeftMapping = layout_left::mapping; + if constexpr (Ext::rank() <= 1) { + LeftMapping left_mapping{ext}; + [[maybe_unused]] Mapping m1{left_mapping}; + [[maybe_unused]] Mapping2 m2{left_mapping}; + assert(m1 == m2); + static_assert(is_nothrow_constructible_v); + static_assert(is_nothrow_constructible_v); + } else { + static_assert(!is_constructible_v); + static_assert(!is_constructible_v); + } + // Other tests are defined in 'check_construction_from_other_left_mapping' function + } + + { // Check construction from layout_stride::mapping + array strides{}; + if constexpr (Ext::rank() > 0) { + strides.back() = 1; + for (size_t i = Ext::rank() - 1; i-- > 0;) { +#pragma warning(push) +#pragma warning(disable : 28020) // TRANSITION, DevCom-923103 + strides[i] = static_cast(strides[i + 1] * ext.extent(i + 1)); +#pragma warning(pop) + } + } + + layout_stride::mapping m1{ext, strides}; + Mapping m2{m1}; + assert(m1.extents() == m2.extents()); + // Other tests are defined in 'check_construction_from_other_stride_mapping' function + } + + Mapping m{ext}; // For later use + + { // Check 'extents' function + assert(m.extents() == ext); + static_assert(noexcept(m.extents())); + } + + { // Check 'required_span_size' function + same_as decltype(auto) rss = m.required_span_size(); + assert(rss == static_cast((ext.extent(Indices) * ... * 1))); + static_assert(noexcept(m.required_span_size())); + // Other tests are defined in 'check_mapping_properties' + } + + { // Check operator() + assert(m(((void) Indices, 0)...) == 0); + assert(m((ext.extent(Indices) - 1)...) == static_cast((ext.extent(Indices) * ... * 1)) - 1); + static_assert(noexcept(m(((void) Indices, 0)...))); + static_assert(noexcept(m((ext.extent(Indices) - 1)...))); + // Other tests are defined in 'check_call_operator' function + } + + { // Check 'is_always_[unique/exhaustive/strided]' functions + static_assert(Mapping::is_always_unique()); + static_assert(Mapping::is_always_exhaustive()); + static_assert(Mapping::is_always_strided()); + } + + { // Check 'is_[unique/exhaustive/strided]' functions + static_assert(Mapping::is_unique()); + static_assert(Mapping::is_exhaustive()); + static_assert(Mapping::is_strided()); + // Other tests are defined in 'check_mapping_properties' + } + + if constexpr (Ext::rank() > 0) { // Check 'stride' function + const IndexType expected_stride0 = static_cast((ext.extent(Indices) * ... * 1) / ext.extent(0)); + assert(m.stride(0) == expected_stride0); + assert(m.stride(Ext::rank() - 1) == 1); + static_assert(noexcept(m.stride(Ext::rank() - 1))); + static_assert(noexcept(m.stride(0))); + // Other tests are defined in 'check_stride_function' + } else { + static_assert(!CheckStrideMemberFunction); + } + + { // Check comparisons + assert(m == m); + assert(!(m != m)); + static_assert(noexcept(m == m)); + static_assert(noexcept(m != m)); + // Other tests are defined in 'check_comparisons' function + } +} + +void check_mapping_properties() { + if constexpr (!is_permissive) { + auto check = [](const auto& mapping) { + const auto props = get_mapping_properties(mapping); + assert(props.req_span_size == mapping.required_span_size()); + assert(props.uniqueness); + assert(props.exhaustiveness); + assert(props.strideness); + }; + + using M1 = layout_right::mapping>; + check(M1{}); + + using M2 = layout_right::mapping>; + check(M2{M2::extents_type{2, 6}}); + + using M3 = layout_right::mapping>; + check(M3{M3::extents_type{4, 3, 5, 4}}); + } +} + +constexpr void check_construction_from_extents() { + using Ext = extents; + Ext ext; + + { // Check construction from 'extents_type' + layout_right::mapping mp(ext); + assert(mp.extents() == ext); + static_assert(is_nothrow_constructible_v); + } + + { // Check construction from other extents + layout_right::mapping> mp(ext); + assert(mp.extents() == ext); + static_assert(is_nothrow_constructible_v); + } +} + +constexpr void check_construction_from_other_right_mapping() { + { // Check invalid construction + using Mapping = layout_right::mapping>; + static_assert(!is_constructible_v>>); + static_assert(!is_constructible_v>>); + } + + { // Check implicit conversions + static_assert(!NotImplicitlyConstructibleFrom>, + layout_right::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_right::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_right::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_right::mapping>>); + } + + { // Check effects + layout_right::mapping> m1; + layout_right::mapping> m2{m1}; + assert(m2.extents().extent(0) == 6); + assert(m2.extents().extent(1) == 2); + assert(m2.extents().extent(2) == 6); + assert(m1.extents() == m2.extents()); + } +} + +constexpr void check_construction_from_other_left_mapping() { + { // Check construction from layout_left::mapping with various values of E::rank() + static_assert(is_nothrow_constructible_v>, + layout_left::mapping>>); + static_assert(is_nothrow_constructible_v>, + layout_left::mapping>>); + static_assert( + !is_constructible_v>, layout_left::mapping>>); + static_assert( + !is_constructible_v>, layout_left::mapping>>); + } + + { // Check construction from layout_left::mapping when E is invalid + using Mapping = layout_right::mapping>; + static_assert(!is_constructible_v>>); + static_assert(!is_constructible_v>>); + } + + { // Check implicit conversions + using Mapping = layout_right::mapping>; + static_assert(!NotImplicitlyConstructibleFrom>>); + static_assert(NotImplicitlyConstructibleFrom>>); + static_assert(NotImplicitlyConstructibleFrom>>); + } + + { // Check effects + layout_left::mapping> m1; + layout_right::mapping> m2{m1}; + assert(m2.extents().extent(0) == 8); + } +} + +constexpr void check_construction_from_other_stride_mapping() { + { // Check construction from layout_stride::mapping with various values of E::rank() + static_assert(is_nothrow_constructible_v>, + layout_stride::mapping>>); + static_assert(is_nothrow_constructible_v>, + layout_stride::mapping>>); + static_assert(is_nothrow_constructible_v>, + layout_stride::mapping>>); + static_assert(is_nothrow_constructible_v>, + layout_stride::mapping>>); + } + + { // Check construction from layout_stride::mapping when E is invalid + using Mapping = layout_right::mapping>; + static_assert(!is_constructible_v>>); + static_assert(!is_constructible_v>>); + } + + { // Check correctness + using Ext = extents; + layout_stride::mapping m1{Ext{}, array{72, 24, 12, 4, 1}}; + layout_right::mapping m2{m1}; + assert(m2.extents().extent(0) == 4); + assert(m2.extents().extent(1) == 3); + assert(m2.stride(2) == 12); + assert(m2.stride(3) == 4); + assert(m2.extents() == m1.extents()); + } + + { // Check implicit conversions + static_assert( + !NotImplicitlyConstructibleFrom>, layout_stride::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_stride::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_stride::mapping>>); + static_assert(NotImplicitlyConstructibleFrom>, + layout_stride::mapping>>); + } +} + +constexpr void check_call_operator() { + { // Check call with invalid amount of indices + using Mapping = layout_right::mapping>; + static_assert(!CheckCallOperatorOfLayoutMapping); + static_assert(!CheckCallOperatorOfLayoutMapping); + static_assert(CheckCallOperatorOfLayoutMapping); + static_assert(!CheckCallOperatorOfLayoutMapping); + } + + { // Check call with invalid types + using Mapping = layout_right::mapping>; + static_assert(CheckCallOperatorOfLayoutMapping); + static_assert(CheckCallOperatorOfLayoutMapping); + static_assert(CheckCallOperatorOfLayoutMapping>); + static_assert(CheckCallOperatorOfLayoutMapping>); + static_assert(!CheckCallOperatorOfLayoutMapping); + } + + { // Check call with types that might throw during conversion + using Mapping = layout_right::mapping>; + static_assert(CheckCallOperatorOfLayoutMapping>); + static_assert(!CheckCallOperatorOfLayoutMapping>); + } + + { // Check various mappings + layout_right::mapping> m1; + assert(m1() == 0); + + layout_right::mapping> m2{dextents{4}}; + assert(m2(0) == 0); + assert(m2(1) == 1); + assert(m2(2) == 2); + assert(m2(3) == 3); + + layout_right::mapping> m3; + assert(m3(0, 0) == 0); + assert(m3(0, 1) == 1); + assert(m3(1, 0) == 5); + assert(m3(1, 1) == 6); + assert(m3(1, 2) == 7); + assert(m3(2, 1) == 11); + assert(m3(2, 2) == 12); + assert(m3(3, 4) == 19); + } +} + +constexpr void check_stride_function() { + layout_right::mapping> prime_mapping; + + { // Check return type + same_as decltype(auto) s = prime_mapping.stride(0); + assert(s == 1155); // 11 * 7 * 5 * 3 + } + + { // Check that argument is 'rank_type' + struct ConvertibleToRankType { + constexpr operator integral auto() const { + return 0; + } + + constexpr operator size_t() const { // NB: 'rank_type' is always 'size_t' + return 1; + } + }; + + assert(prime_mapping.stride(ConvertibleToRankType{}) == 385); // 11 * 7 * 5 + } + + { // Check other strides + assert(prime_mapping.stride(2) == 77); + assert(prime_mapping.stride(3) == 11); + assert(prime_mapping.stride(4) == 1); + } +} + +constexpr void check_comparisons() { + using StaticMapping = layout_right::mapping>; + using DynamicMapping = layout_right::mapping>; + + { // Check equality_comparable_with concept + static_assert(equality_comparable_with); + static_assert(!equality_comparable_with>>); + static_assert(!equality_comparable_with>>); + } + + { // Check correctness + StaticMapping m1; + DynamicMapping m2{dextents{4, 4}}; + DynamicMapping m3{dextents{2, 2}}; + assert(m1 == m2); + assert(m2 != m3); + assert(m1 != m3); + } +} + +constexpr void check_ctad() { + using Ext = extents; + layout_right::mapping m{Ext{}}; + static_assert(same_as>); + assert(m.extents().extent(0) == 5); + assert(m.stride(1) == 1); +} + +constexpr void check_correctness() { + { // empty extents + const array vals{}; + mdspan, layout_right> nothing{vals.data()}; + assert(nothing.size() == 1); + } + + { // regular vector + const array vals{2, 1, 0}; + mdspan, layout_right> vec{vals.data()}; + +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 + assert(vec[0] == 2); + assert(vec[1] == 1); + assert(vec[2] == 0); +#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(__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 __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(__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(__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 __cpp_multidimensional_subscript // TRANSITION, P2128R6 + assert((tensor[0, 0, 0] == 23)); + assert((tensor[0, 0, 1] == 22)); + assert((tensor[0, 1, 0] == 21)); + assert((tensor[0, 1, 1] == 20)); + assert((tensor[1, 0, 0] == 17)); + assert((tensor[1, 0, 1] == 16)); + assert((tensor[1, 1, 0] == 15)); + assert((tensor[1, 1, 1] == 14)); + assert((tensor[2, 2, 1] == 6)); + assert((tensor[3, 2, 1] == 0)); +#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)); + assert((tensor[array{0, 1, 1}] == 20)); + assert((tensor[array{1, 0, 0}] == 17)); + assert((tensor[array{1, 0, 1}] == 16)); + assert((tensor[array{1, 1, 0}] == 15)); + assert((tensor[array{1, 1, 1}] == 14)); + assert((tensor[array{2, 2, 1}] == 6)); + assert((tensor[array{3, 2, 1}] == 0)); +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ + } + + { // 3x2x3x2 tensor + const array vals{35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 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(), 2, 2}; + +#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)); + assert((tensor[0, 0, 1, 1] == 32)); + assert((tensor[0, 1, 0, 0] == 29)); + assert((tensor[0, 1, 0, 1] == 28)); + assert((tensor[0, 1, 1, 0] == 27)); + assert((tensor[0, 1, 1, 1] == 26)); + assert((tensor[1, 0, 0, 0] == 23)); + assert((tensor[1, 0, 0, 1] == 22)); + assert((tensor[1, 0, 1, 0] == 21)); + assert((tensor[1, 0, 1, 1] == 20)); + assert((tensor[1, 1, 0, 0] == 17)); + assert((tensor[1, 1, 0, 1] == 16)); + assert((tensor[1, 1, 1, 0] == 15)); + assert((tensor[1, 1, 1, 1] == 14)); + assert((tensor[2, 0, 2, 0] == 7)); + assert((tensor[2, 1, 2, 1] == 0)); +#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)); + assert((tensor[array{0, 0, 1, 1}] == 32)); + assert((tensor[array{0, 1, 0, 0}] == 29)); + assert((tensor[array{0, 1, 0, 1}] == 28)); + assert((tensor[array{0, 1, 1, 0}] == 27)); + assert((tensor[array{0, 1, 1, 1}] == 26)); + assert((tensor[array{1, 0, 0, 0}] == 23)); + assert((tensor[array{1, 0, 0, 1}] == 22)); + assert((tensor[array{1, 0, 1, 0}] == 21)); + assert((tensor[array{1, 0, 1, 1}] == 20)); + assert((tensor[array{1, 1, 0, 0}] == 17)); + assert((tensor[array{1, 1, 0, 1}] == 16)); + assert((tensor[array{1, 1, 1, 0}] == 15)); + 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(__cpp_multidimensional_subscript) ^^^ + } +} + +// When 'M::extents_type::rank_dynamic()' is equal to 0 then 'is_empty_v' should be true (MSVC STL specific behavior) +static_assert(!is_empty_v>>); +static_assert(!is_empty_v>>); +static_assert(is_empty_v>>); +static_assert(is_empty_v>>); + +constexpr bool test() { + check_members_with_various_extents([](const E& e) { check_members(e, make_index_sequence{}); }); + if (!is_constant_evaluated()) { // too heavy for compile time + check_mapping_properties(); + } + check_construction_from_extents(); + check_construction_from_other_right_mapping(); + check_construction_from_other_left_mapping(); + check_construction_from_other_stride_mapping(); + check_call_operator(); + check_stride_function(); + check_comparisons(); + check_ctad(); + check_correctness(); + + return true; +} + +int main() { + static_assert(test()); + test(); +} diff --git a/tests/std/tests/P0009R18_mdspan_layout_right_death/env.lst b/tests/std/tests/P0009R18_mdspan_layout_right_death/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_right_death/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_layout_right_death/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_right_death/test.cpp new file mode 100644 index 00000000000..4e643353f18 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_right_death/test.cpp @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include +#include +#include + +#include + +using namespace std; + +void test_construction_from_extents_type_with_signed_index_type() { + using Ext = dextents; + // The size of the multidimensional index space e must be representable as a value of type index_type + [[maybe_unused]] layout_right::mapping m{Ext{5, 4, 7}}; +} + +void test_construction_from_extents_type_with_unsigned_index_type() { + using Ext = dextents; + // The size of the multidimensional index space e must be representable as a value of type index_type + [[maybe_unused]] layout_right::mapping m{Ext{5, 10, 6}}; +} + +void test_construction_from_other_right_mapping() { + layout_right::mapping> m1{dextents{256}}; + // Value of other.required_span_size() must be representable as a value of type index_type + layout_right::mapping> m2{m1}; +} + +void test_construction_from_other_left_mapping() { + layout_left::mapping> m1{dextents{256}}; + // Value of other.required_span_size() must be representable as a value of type index_type + layout_right::mapping> m2{m1}; +} + +void test_construction_from_other_stride_mapping_1() { + using Ext = extents; + layout_stride::mapping m1{Ext{}, array{1, 2}}; + // For all r in the range [0, extents_type::rank()), other.stride(r) must be equal to + // extents().rev-prod-of-extents(r) + layout_right::mapping m2{m1}; +} + +void test_construction_from_other_stride_mapping_2() { + layout_stride::mapping> m1{dextents{256}, array{1}}; + // Value of other.required_span_size() must be representable as a value of type index_type + layout_right::mapping> m2{m1}; +} + +void test_call_operator() { + layout_right::mapping> m; + // Value of extents_type::index-cast(i) must be a multidimensional index in extents_ + (void) m(4, 3, 3); +} + +void test_stride_function() { + layout_right::mapping> m; + // Value of i must be less than extents_type::rank() + (void) m.stride(1); +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + exec.add_death_tests({ + test_construction_from_extents_type_with_signed_index_type, + test_construction_from_extents_type_with_unsigned_index_type, + test_construction_from_other_right_mapping, + test_construction_from_other_left_mapping, + test_construction_from_other_stride_mapping_1, + test_construction_from_other_stride_mapping_2, + test_call_operator, + test_stride_function, + }); + return exec.run(argc, argv); +} diff --git a/tests/std/tests/P0009R18_mdspan_layout_stride/env.lst b/tests/std/tests/P0009R18_mdspan_layout_stride/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_stride/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_layout_stride/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp new file mode 100644 index 00000000000..d276b22e7fb --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp @@ -0,0 +1,870 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +struct CmpEqual { + template + [[nodiscard]] constexpr bool operator()(T t, U u) const noexcept { + return cmp_equal(t, u); + } +}; + +struct NotLayoutMappingAlikeAtAll { + template + class mapping : public layout_right::mapping { + public: + using layout_type = NotLayoutMappingAlikeAtAll; + + private: + using layout_right::mapping::is_always_exhaustive; + }; +}; + +static_assert(!_Layout_mapping_alike>>); + +enum class AlwaysUnique : bool { no, yes }; +enum class AlwaysStrided : bool { no, yes }; + +template +struct LyingLayout { + template + class mapping : public layout_left::mapping { + public: + using layout_type = LyingLayout; + + constexpr bool is_unique() const { + return is_unique(); + } + + constexpr bool is_exhaustive() const { + return is_exhaustive(); + } + + constexpr bool is_strided() const { + return is_strided(); + } + + static constexpr bool is_always_unique() { + // might be a lie, allowed by the standard (N4950 [mdspan.layout.reqmts]/23 Note 5) + return to_underlying(Unique); + } + + static constexpr bool is_always_exhaustive() { + return layout_left::mapping::is_always_exhaustive(); + } + + static constexpr bool is_always_strided() { + // might be a lie, allowed by the standard (N4950 [mdspan.layout.reqmts]/27 Note 7) + return to_underlying(Strided); + } + }; +}; + +static_assert( + check_layout_mapping_policy_requirements, extents>()); +static_assert( + check_layout_mapping_policy_requirements, dextents>()); + +struct HollowLayout { + template + requires (Extents::rank() == 0) + class mapping : public layout_right::mapping { + public: + using index_type = Extents::index_type; + using layout_type = HollowLayout; + + constexpr index_type operator()() const noexcept { + return 1; // NB: used by 'check_comparisons' (OFFSET(*this) != 0) + } + + constexpr index_type required_span_size() const noexcept { + return 2; + } + }; +}; + +static_assert(check_layout_mapping_policy_requirements>()); + +template +constexpr void do_check_members(const extents& ext, + const array& strs, index_sequence) { + using Ext = extents; + using Strides = array; + using Mapping = layout_stride::mapping; + + // layout_stride meets the layout mapping policy requirements and is a trivial type + static_assert(check_layout_mapping_policy_requirements()); + static_assert(is_trivial_v); + + // layout_stride::mapping is a trivially copyable type that models regular for each Ext + static_assert(is_trivially_copyable_v); + static_assert(regular); + + // Check member types + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + static_assert(same_as); + + { // Check default and copy constructor + const Mapping m; + const Mapping cpy = m; + const layout_right::mapping right_mapping; + assert(m == right_mapping); + assert(cpy == m); + static_assert(is_nothrow_default_constructible_v); + static_assert(is_nothrow_copy_constructible_v); + } + + { // Check copy assignment operator + const Mapping m; + Mapping cpy; + cpy = m; + assert(cpy == m); + static_assert(is_nothrow_copy_assignable_v); + } + + { // Check construction from extents_type and array + Mapping m{ext, strs}; + assert(m.extents() == ext); + assert(ranges::equal(m.strides(), strs, CmpEqual{})); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_extents_and_array_or_span' function + } + + { // Check construction from extents_type and span + using Span = span; + Mapping m{ext, Span{strs}}; + assert(m.extents() == ext); + assert(ranges::equal(m.strides(), strs, CmpEqual{})); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_extents_and_array_or_span' function + } + + using OtherIndexType = long long; + using Ext2 = extents; + using Mapping2 = layout_stride::mapping; + + { // Check construction from other mappings + Mapping m1{ext, strs}; + Mapping2 m2{m1}; + assert(m1 == m2); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_other_mappings' function + } + + Mapping m{ext, strs}; // For later use + + { // Check 'extents' function + same_as decltype(auto) ext2 = m.extents(); + assert(ext2 == ext); + static_assert(noexcept(m.extents())); + } + + { // Check 'strides' function + same_as decltype(auto) strs2 = m.strides(); + assert(ranges::equal(strs2, strs, CmpEqual{})); + static_assert(noexcept(m.strides())); + // Other tests are defined in 'check_stride_function' + } + + { // Check 'required_span_size' function + if (((ext.extent(Indices) == 0) || ...)) { + assert(m.required_span_size() == 0); + } else { + const IndexType expected_value = + static_cast((((ext.extent(Indices) - 1) * strs[Indices]) + ... + 1)); + assert(m.required_span_size() == expected_value); + } + static_assert(noexcept(m.required_span_size())); + // Other tests are defined in 'check_required_span_size' and 'check_mapping_properties' functions + } + + // Call operator() is tested in 'check_call_operator' function + + { // Check 'is_always_[unique/exhaustive/strided]' functions + static_assert(Mapping::is_always_unique()); + static_assert(!Mapping::is_always_exhaustive()); + static_assert(Mapping::is_always_strided()); + } + + { // Check 'is_[unique/strided]' functions + static_assert(Mapping::is_unique()); + static_assert(Mapping::is_strided()); + // Tests of 'is_exhaustive' are defined in 'check_is_exhaustive' function + // Other tests are defined in 'check_mapping_properties' + } + + { // Check 'stride' function + for (size_t i = 0; i < strs.size(); ++i) { + same_as decltype(auto) s = m.stride(i); +#pragma warning(push) +#pragma warning(disable : 28020) // TRANSITION, DevCom-923103 + assert(cmp_equal(strs[i], s)); +#pragma warning(pop) + } + } + + { // Check comparisons + assert(m == m); + assert(!(m != m)); + static_assert(noexcept(m == m)); + static_assert(noexcept(m != m)); + // Other tests are defined in 'check_comparisons' function + } +} + +template +constexpr void check_members_with_different_strides_index_type( + extents ext, const array& strides) { + array test_strides; + ranges::transform(strides, test_strides.begin(), [](auto i) { return static_cast(i); }); + do_check_members(ext, test_strides, make_index_sequence{}); +} + +template +constexpr void check_members(extents ext, const array& strides) { + // Check signed strides + check_members_with_different_strides_index_type(ext, strides); + check_members_with_different_strides_index_type(ext, strides); + check_members_with_different_strides_index_type(ext, strides); + check_members_with_different_strides_index_type(ext, strides); + check_members_with_different_strides_index_type(ext, strides); + + // Check unsigned strides + check_members_with_different_strides_index_type(ext, strides); + check_members_with_different_strides_index_type(ext, strides); + check_members_with_different_strides_index_type(ext, strides); + check_members_with_different_strides_index_type(ext, strides); + check_members_with_different_strides_index_type(ext, strides); +} + +void check_mapping_properties() { + if constexpr (!is_permissive) { + auto check = [](const auto& mapping, const bool expected_exhaustiveness) { + const auto props = get_mapping_properties(mapping); + assert(props.req_span_size == mapping.required_span_size()); + assert(props.uniqueness); + assert(props.exhaustiveness == expected_exhaustiveness); + assert(props.exhaustiveness == mapping.is_exhaustive()); + assert(props.strideness); + }; + + { // Check exhaustive mappings + using M1 = layout_stride::mapping>; + check(M1{M1::extents_type{}, array{6, 1, 2}}, true); + + using M2 = layout_stride::mapping>; + check(M2{M2::extents_type{6, 7}, array{1, 48, 6}}, true); + + using M3 = layout_stride::mapping>; + check(M3{M3::extents_type{3, 5, 2, 4}, array{20, 1, 60, 5}}, true); + } + + { // Check non-exhaustive mappings + using M1 = layout_stride::mapping>; + check(M1{M1::extents_type{}, array{9, 18, 1}}, false); + + using M2 = layout_stride::mapping>; + check(M2{M2::extents_type{4, 3}, array{12, 36, 1}}, false); + + using M3 = layout_stride::mapping>; + check(M3{M3::extents_type{4, 3, 2}, array{8, 32, 2}}, false); + } + } +} + +constexpr void check_construction_from_extents_and_array_or_span() { + { // Check invalid construction + using Mapping = layout_stride::mapping>; + static_assert(!is_constructible_v, array>); + static_assert(!is_constructible_v, span>); + static_assert(!is_constructible_v, array>); + static_assert(!is_constructible_v, span>); + static_assert(!is_constructible_v, array>); + static_assert(!is_constructible_v, span>); + static_assert(!is_constructible_v, array>); + static_assert(!is_constructible_v, span>); + static_assert(!is_constructible_v, array>); + static_assert(!is_constructible_v, span>); + static_assert(!is_constructible_v, array, 2>>); + static_assert(!is_constructible_v, span, 2>>); + } + + using Ext = extents; + Ext ext; + + { // Check construction from 'extents_type' + array strs{24, 1, 4}; + layout_stride::mapping m1(ext, strs); + assert(m1.extents() == ext); + static_assert(is_nothrow_constructible_v); + + span s{strs}; + layout_stride::mapping m2(ext, s); + assert(m2.extents() == ext); + static_assert(is_nothrow_constructible_v); + } + + { // Check construction from other extents + using Ext2 = extents; + + const array strs{1, 12, 2}; + layout_stride::mapping m1(ext, strs); + assert(m1.extents() == ext); + static_assert(is_nothrow_constructible_v); + + const span s{strs}; + layout_stride::mapping m2(ext, s); + static_assert(is_nothrow_constructible_v); + } +} + +constexpr void check_construction_from_other_mappings() { + { // Check valid construction + using Mapping = layout_stride::mapping>; + static_assert(is_nothrow_constructible_v>>); + static_assert(is_nothrow_constructible_v>>); + static_assert(is_nothrow_constructible_v>>); + static_assert(is_nothrow_constructible_v>>); + static_assert(is_nothrow_constructible_v>>); + static_assert(is_nothrow_constructible_v::mapping>>); + } + + { // Check invalid construction + using Mapping = layout_stride::mapping>; + 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::mapping>>); + static_assert(!is_constructible_v::mapping>>); + static_assert(!is_constructible_v::mapping>>); + } + + { // Check construction from layout_left::mapping + layout_left::mapping> left_mapping{dextents{4, 3, 2}}; + layout_stride::mapping> strided_mapping{left_mapping}; + assert(ranges::equal(strided_mapping.strides(), array{1, 4, 12}, CmpEqual{})); + } + + { // Check construction from layout_right::mapping + layout_right::mapping> right_mapping{dextents{4, 3, 2}}; + layout_stride::mapping> strided_mapping{right_mapping}; + assert(ranges::equal(strided_mapping.strides(), array{6, 2, 1}, CmpEqual{})); + } +} + +constexpr void check_required_span_size() { + { // Check N4950 [mdspan.layout.stride.expo]/1.1: Ext::rank() == 0 + using M1 = layout_stride::mapping>; + static_assert(M1{}.required_span_size() == 1); + + M1 m1; + assert(m1.required_span_size() == 1); + } + + { // Check N4950 [mdspan.layout.stride.expo]/1.2: size of the multidimensional index space e is 0 + using M1 = layout_stride::mapping>; + static_assert(M1{}.required_span_size() == 0); + + layout_stride::mapping> m2{dextents{3, 0, 3, 3}, array{1, 3, 1, 1}}; + assert(m2.required_span_size() == 0); + } + + { // Check N4950 [mdspan.layout.stride.expo]/1.3: final case + using M1 = layout_stride::mapping>; + static_assert(M1{}.required_span_size() == 36); + + layout_stride::mapping> m2{dextents{4, 3, 4}, array{1, 4, 12}}; + assert(m2.required_span_size() == 48); + } +} + +constexpr void check_is_exhaustive() { + { // Check exhaustive mappings (all possibilities) + using E = extents; + assert((layout_stride::mapping{E{}, array{1, 2, 6}}.is_exhaustive())); + assert((layout_stride::mapping{E{}, array{1, 10, 2}}.is_exhaustive())); + assert((layout_stride::mapping{E{}, array{3, 1, 6}}.is_exhaustive())); + assert((layout_stride::mapping{E{}, array{15, 1, 3}}.is_exhaustive())); + assert((layout_stride::mapping{E{}, array{5, 10, 1}}.is_exhaustive())); + assert((layout_stride::mapping{E{}, array{15, 5, 1}}.is_exhaustive())); + } + + { // Check non-exhaustive mappings + using E = extents; + assert((!layout_stride::mapping{E{}, array{1, 2, 12}}.is_exhaustive())); + assert((!layout_stride::mapping{E{}, array{8, 18, 1}}.is_exhaustive())); + assert((!layout_stride::mapping{E{}, array{5, 1, 12}}.is_exhaustive())); + } +} + +constexpr void check_call_operator() { + { // Check call with invalid amount of indices + using Mapping = layout_stride::mapping>; + static_assert(!CheckCallOperatorOfLayoutMapping); + static_assert(!CheckCallOperatorOfLayoutMapping); + static_assert(CheckCallOperatorOfLayoutMapping); + static_assert(!CheckCallOperatorOfLayoutMapping); + } + + { // Check call with invalid types + using Mapping = layout_stride::mapping>; + static_assert(CheckCallOperatorOfLayoutMapping); + static_assert(CheckCallOperatorOfLayoutMapping); + static_assert(CheckCallOperatorOfLayoutMapping>); + static_assert(CheckCallOperatorOfLayoutMapping>); + static_assert(!CheckCallOperatorOfLayoutMapping); + } + + { // Check call with types that might throw during conversion + using Mapping = layout_stride::mapping>; + static_assert(CheckCallOperatorOfLayoutMapping>); + static_assert(!CheckCallOperatorOfLayoutMapping>); + } + + { // Check various mappings + layout_stride::mapping> m1; + assert(m1() == 0); + + layout_stride::mapping> m2{dextents{4}, array{1}}; + assert(m2(0) == 0); + assert(m2(1) == 1); + assert(m2(2) == 2); + assert(m2(3) == 3); + + layout_stride::mapping> m3{{}, array{1, 5}}; // non-exhaustive mapping + assert(!m3.is_exhaustive()); + assert(m3(0, 0) == 0); + assert(m3(0, 1) == 5); + assert(m3(0, 2) == 10); + assert(m3(0, 3) == 15); + assert(m3(0, 4) == 20); + assert(m3(1, 0) == 1); + assert(m3(1, 1) == 6); + assert(m3(1, 2) == 11); + assert(m3(1, 3) == 16); + assert(m3(1, 4) == 21); + assert(m3(2, 0) == 2); + assert(m3(2, 1) == 7); + assert(m3(2, 2) == 12); + assert(m3(2, 3) == 17); + assert(m3(3, 0) == 3); + assert(m3(3, 1) == 8); + assert(m3(3, 2) == 13); + assert(m3(3, 4) == 23); + + layout_stride::mapping> m4{{}, array{15, 1, 3}}; // exhaustive mapping + assert(m4.is_exhaustive()); + assert(m4(0, 0, 0) == 0); + assert(m4(0, 0, 1) == 3); + assert(m4(0, 1, 0) == 1); + assert(m4(0, 1, 1) == 4); + assert(m4(1, 0, 0) == 15); + assert(m4(1, 0, 1) == 18); + assert(m4(1, 1, 0) == 16); + assert(m4(1, 1, 1) == 19); + assert(m4(1, 2, 4) == 29); + } +} + +constexpr void check_stride_function() { + using Ext = extents; + layout_stride::mapping even_mapping{Ext{}, array{80, 160, 640, 1, 8}}; + + { // Check return type + same_as decltype(auto) s = even_mapping.stride(0); + assert(s == 80); + } + + { // Check that argument is 'rank_type' + struct ConvertibleToRankType { + constexpr operator integral auto() const { + return 0; + } + + constexpr operator size_t() const { // NB: 'rank_type' is always 'size_t' + return 1; + } + }; + + assert(even_mapping.stride(ConvertibleToRankType{}) == 160); + } + + { // Check other strides + assert(even_mapping.stride(2) == 640); + assert(even_mapping.stride(3) == 1); + assert(even_mapping.stride(4) == 8); + } +} + +constexpr void check_comparisons() { + using E = extents; + using StaticStrideMapping = layout_stride::mapping; + using DynamicStrideMapping = layout_stride::mapping>; + using RightMapping = layout_right::mapping; + using LeftMapping = layout_left::mapping; + + { // Check equality_comparable_with concept (correct comparisons) + static_assert(equality_comparable_with); + static_assert(equality_comparable_with); + static_assert(equality_comparable_with); + static_assert(equality_comparable_with); + static_assert(equality_comparable_with); + } + + { // Check equality_comparable_with concept (incorrect comparisons) + static_assert( + !equality_comparable_with>>); + static_assert(!equality_comparable_with>>); + static_assert(!equality_comparable_with>>); + static_assert(!equality_comparable_with>>); + static_assert(!equality_comparable_with>>); + static_assert(!equality_comparable_with::mapping>>); + static_assert(!equality_comparable_with::mapping>>); + static_assert(!equality_comparable_with::mapping>>); + static_assert(!equality_comparable_with::mapping>>); + } + + { // Check correctness: layout_stride::mapping with layout_stride::mapping + StaticStrideMapping m1{E{}, array{3, 1}}; + DynamicStrideMapping m2{dextents{2, 3}, array{3, 1}}; + same_as decltype(auto) cond = m1 == m2; + assert(cond); // extents are equal, OFFSET(rhs) == 0, strides are equal + + DynamicStrideMapping m3{dextents{2, 3}, array{1, 2}}; + assert(m1 != m3); // extents are equal, OFFSET(rhs) == 0, strides are not equal + assert(m2 != m3); // ditto + + DynamicStrideMapping m4{dextents{1, 3}, array{3, 1}}; + assert(m1 != m4); // extents are not equal, OFFSET(rhs) == 0, strides are equal + assert(m2 != m4); // ditto + assert(m3 != m4); // extents are not equal, OFFSET(rhs) == 0, strides are not equal + + // NB: OFFSET(layout_stride::mapping) is always equal to 0 + + static_assert(noexcept(m1 == m2)); + static_assert(noexcept(m1 != m3)); + } + + { // Check correctness: layout_stride::mapping with layout_left::mapping + LeftMapping m1; + StaticStrideMapping m2{E{}, array{1, 2}}; + same_as decltype(auto) cond = m1 == m2; + assert(cond); // extents are equal, OFFSET(rhs) == 0, strides are equal + + DynamicStrideMapping m3{dextents{2, 3}, array{3, 1}}; + assert(m1 != m3); // extents are equal, OFFSET(rhs) == 0, strides are not equal + assert(m2 != m3); // ditto + + DynamicStrideMapping m4{dextents{2, 1}, array{1, 2}}; + assert(m1 != m4); // extents are not equal, OFFSET(rhs) == 0, strides are equal + assert(m2 != m4); // ditto + assert(m3 != m4); // extents are not equal, OFFSET(rhs) == 0, strides are not equal + + // NB: OFFSET(layout_left::mapping) is always equal to 0 + + static_assert(noexcept(m1 == m2)); + static_assert(noexcept(m1 != m3)); + } + + { // Check correctness: layout_stride::mapping with layout_right::mapping + RightMapping m1; + StaticStrideMapping m2{E{}, array{3, 1}}; + same_as decltype(auto) cond = m1 == m2; + assert(cond); // extents are equal, OFFSET(rhs) == 0, strides are equal + + DynamicStrideMapping m3{dextents{2, 3}, array{1, 2}}; + assert(m1 != m3); // extents are equal, OFFSET(rhs) == 0, strides are not equal + assert(m2 != m3); // ditto + + DynamicStrideMapping m4{dextents{1, 3}, array{3, 1}}; + assert(m1 != m4); // extents are not equal, OFFSET(rhs) == 0, strides are equal + assert(m2 != m4); // ditto + assert(m3 != m4); // extents are not equal, OFFSET(rhs) == 0, strides are not equal + + // NB: OFFSET(layout_right::mapping) is always equal to 0 + + static_assert(noexcept(m1 == m2)); + static_assert(noexcept(m1 != m3)); + } + + { // Check correctness: layout_stride::mapping with LyingLayout::mapping + using CustomMapping = LyingLayout::mapping; + CustomMapping m1; + StaticStrideMapping m2{E{}, array{1, 2}}; + same_as decltype(auto) cond = m1 == m2; + assert(cond); // extents are equal, OFFSET(rhs) == 0, strides are equal + + DynamicStrideMapping m3{dextents{2, 3}, array{3, 1}}; + assert(m1 != m3); // extents are equal, OFFSET(rhs) == 0, strides are not equal + assert(m2 != m3); // ditto + + DynamicStrideMapping m4{dextents{1, 3}, array{1, 2}}; + assert(m1 != m4); // extents are not equal, OFFSET(rhs) == 0, strides are equal + assert(m2 != m4); // ditto + assert(m3 != m4); // extents are not equal, OFFSET(rhs) == 0, strides are not equal + + // NB: OFFSET(CustomMapping) is always equal to 0 + + static_assert(noexcept(m1 == m2)); + static_assert(noexcept(m1 != m3)); + } + + { // Check correctness: layout_stride::mapping with HollowLayout::mapping + HollowLayout::mapping> m1; + if constexpr (!is_permissive) { + if (!is_constant_evaluated()) { // too heavy for compile time + const auto props = get_mapping_properties(m1); + assert(props.req_span_size == m1.required_span_size()); + assert(props.uniqueness); + assert(props.exhaustiveness); + assert(props.strideness); + } + } + + layout_stride::mapping> m2; + same_as decltype(auto) cond = m1 == m2; + assert(!cond); // extents are equal, OFFSET(rhs) != 0, strides are equal + + static_assert(noexcept(m1 == m2)); + static_assert(noexcept(m1 != m2)); + } +} + +constexpr void check_ctad() { + using E = extents; + E e; + + { // E::index_type and array::value_type are the same + array a{1, 2}; + layout_stride::mapping m1{e, a}; + static_assert(same_as>); + assert(m1.extents() == e); + assert(m1.strides() == a); + + span s{a}; + layout_stride::mapping m2{e, s}; + static_assert(same_as>); + assert(m2.extents() == e); + assert(m2.strides() == a); + } + + { // E::index_type and array::value_type are different + array a{1, 2}; + layout_stride::mapping m1{e, a}; + static_assert(same_as>); + assert(m1.extents() == e); + assert(ranges::equal(m1.strides(), a, CmpEqual{})); + + span s{a}; + layout_stride::mapping m2{e, s}; + static_assert(same_as>); + assert(m2.extents() == e); + assert(ranges::equal(m2.strides(), a, CmpEqual{})); + } +} + +constexpr void check_correctness() { + { // empty extents + const array vals{}; + mdspan, layout_stride> nothing{vals.data(), {}}; + assert(nothing.size() == 1); + } + + { // regular vector + using E = extents; + const array vals{1, 2, 3}; + layout_stride::mapping m{E{}, array{1}}; + mdspan, layout_stride> vec{vals.data(), m}; + +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 + assert((vec[0] == 1)); + assert((vec[1] == 2)); + assert((vec[2] == 3)); +#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(__cpp_multidimensional_subscript) ^^^ + } + + { // 2x3 matrix with row-major order + using E = extents; + const array vals{1, 2, 3, 4, 5, 6}; + layout_stride::mapping m{E{}, array{3, 1}}; + mdspan matrix{vals.data(), m}; + +#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(__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(__cpp_multidimensional_subscript) ^^^ + } + + { // 3x2x2 tensor + using E = extents; + const array vals{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + layout_stride::mapping m{E{}, array{8, 1, 6}}; // non-exhaustive mapping + assert(!m.is_exhaustive()); + mdspan tensor{vals.data(), m}; + +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 + assert((tensor[0, 0, 0] == 0)); + assert((tensor[0, 0, 1] == 6)); + assert((tensor[0, 1, 0] == 1)); + assert((tensor[0, 1, 1] == 7)); + assert((tensor[1, 0, 0] == 8)); + assert((tensor[1, 0, 1] == 14)); + assert((tensor[1, 1, 0] == 9)); + assert((tensor[1, 1, 1] == 15)); + assert((tensor[2, 0, 0] == 16)); + assert((tensor[2, 0, 1] == 22)); + assert((tensor[2, 1, 0] == 17)); + assert((tensor[2, 1, 1] == 23)); +#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)); + assert((tensor[array{0, 1, 1}] == 7)); + assert((tensor[array{1, 0, 0}] == 8)); + assert((tensor[array{1, 0, 1}] == 14)); + assert((tensor[array{1, 1, 0}] == 9)); + assert((tensor[array{1, 1, 1}] == 15)); + assert((tensor[array{2, 0, 0}] == 16)); + assert((tensor[array{2, 0, 1}] == 22)); + assert((tensor[array{2, 1, 0}] == 17)); + assert((tensor[array{2, 1, 1}] == 23)); +#endif // ^^^ !defined(__cpp_multidimensional_subscript) ^^^ + } + + { // 2x3x3x2 tensor + using E = extents; + const array vals{0, 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, 31, 32, 33, 34, 35}; + layout_stride::mapping m{E{}, array{18, 1, 3, 9}}; // exhaustive mapping + assert(m.is_exhaustive()); + mdspan tensor{vals.data(), m}; + +#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)); + assert((tensor[0, 0, 1, 1] == 12)); + assert((tensor[0, 1, 0, 0] == 1)); + assert((tensor[0, 1, 0, 1] == 10)); + assert((tensor[0, 1, 1, 0] == 4)); + assert((tensor[0, 1, 1, 1] == 13)); + assert((tensor[1, 0, 0, 0] == 18)); + assert((tensor[1, 0, 0, 1] == 27)); + assert((tensor[1, 0, 1, 0] == 21)); + assert((tensor[1, 0, 1, 1] == 30)); + assert((tensor[1, 1, 0, 0] == 19)); + assert((tensor[1, 1, 0, 1] == 28)); + assert((tensor[1, 1, 1, 0] == 22)); + assert((tensor[1, 1, 1, 1] == 31)); + assert((tensor[0, 2, 2, 0] == 8)); + assert((tensor[1, 2, 2, 1] == 35)); +#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)); + assert((tensor[array{0, 0, 1, 1}] == 12)); + assert((tensor[array{0, 1, 0, 0}] == 1)); + assert((tensor[array{0, 1, 0, 1}] == 10)); + assert((tensor[array{0, 1, 1, 0}] == 4)); + assert((tensor[array{0, 1, 1, 1}] == 13)); + assert((tensor[array{1, 0, 0, 0}] == 18)); + assert((tensor[array{1, 0, 0, 1}] == 27)); + assert((tensor[array{1, 0, 1, 0}] == 21)); + assert((tensor[array{1, 0, 1, 1}] == 30)); + assert((tensor[array{1, 1, 0, 0}] == 19)); + assert((tensor[array{1, 1, 0, 1}] == 28)); + assert((tensor[array{1, 1, 1, 0}] == 22)); + 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(__cpp_multidimensional_subscript) ^^^ + } +} + +// When 'M::extents_type::rank()' is equal to 0 then 'is_empty_v' should be true (MSVC STL specific behavior) +static_assert(!is_empty_v>>); +static_assert(!is_empty_v>>); +static_assert(!is_empty_v>>); +static_assert(is_empty_v>>); + +constexpr bool test() { + // Check signed integers + check_members(extents{5}, array{1}); + check_members(extents{}, array{1, 6}); + check_members(extents{3}, array{1, 3}); + check_members(extents{}, array{1}); + check_members(extents{3}, array{1, 3, 6}); + + // Check unsigned integers + check_members(extents{5}, array{1}); + check_members(extents{}, array{1, 6}); + check_members(extents{3}, array{1, 3}); + check_members(extents{}, array{1}); + check_members(extents{3}, array{1, 3, 6}); + + // Check degenerate extents + check_members(extents{}, array{}); + check_members(extents{}, array{}); + + if (!is_constant_evaluated()) { // too heavy for compile time + check_mapping_properties(); + } + check_construction_from_extents_and_array_or_span(); + check_construction_from_other_mappings(); + check_required_span_size(); + check_is_exhaustive(); + check_call_operator(); + check_stride_function(); + check_comparisons(); + check_ctad(); + check_correctness(); + + return true; +} + +int main() { + static_assert(test()); + test(); +} diff --git a/tests/std/tests/P0009R18_mdspan_layout_stride_death/env.lst b/tests/std/tests/P0009R18_mdspan_layout_stride_death/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_stride_death/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_layout_stride_death/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_stride_death/test.cpp new file mode 100644 index 00000000000..3663f784d44 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_stride_death/test.cpp @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include +#include +#include + +#include +#include + +using namespace std; + +void test_default_construction() { + using Ext = extents; + // Value of layout_stride::mapping().required_span_size() must be + // representable as a value of type index_type + [[maybe_unused]] layout_stride::mapping m{}; // NB: strides are [140, 35, 7, 1] +} + +void test_construction_from_extents_and_array_1() { + // Value of s[i] must be greater than 0 for all i in the range [0, rank_) + [[maybe_unused]] layout_stride::mapping> m1{extents{}, array{-1}}; +} + +void test_construction_from_extents_and_array_2() { + using Ext = extents; + // REQUIRED-SPAN-SIZE(e, s) must be representable as a value of type index_type + [[maybe_unused]] layout_stride::mapping m{Ext{}, array{2}}; +} + + +void test_construction_from_extents_and_span_1() { + array a{-1}; + // Value of s[i] must be greater than 0 for all i in the range [0, rank_) + [[maybe_unused]] layout_stride::mapping> m{extents{}, span{a}}; +} + +void test_construction_from_extents_and_span_2() { + using Ext = extents; + array, 1> a{{{.val = 2}}}; + const span s{a}; + // REQUIRED-SPAN-SIZE(e, s) must be representable as a value of type index_type + [[maybe_unused]] layout_stride::mapping m{Ext{}, s}; +} + +void test_construction_from_strided_layout_mapping() { + layout_right::mapping> m1; + // Value of other.required_span_size() must be representable as a value of type index_type + [[maybe_unused]] layout_stride::mapping> m2{m1}; +} + +void test_call_operator() { + layout_stride::mapping> m; + // Value of extents_type::index-cast(i) must be a multidimensional index in extents_ + (void) m(4, 3, 3); +} + +void test_stride_with_empty_extents() { + layout_stride::mapping> m; + // The argument to stride must be nonnegative and less than extents_type::rank() + (void) m.stride(0); +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + exec.add_death_tests({ + test_default_construction, + test_construction_from_extents_and_array_1, + test_construction_from_extents_and_array_2, + test_construction_from_extents_and_span_1, + test_construction_from_extents_and_span_2, + test_construction_from_strided_layout_mapping, + test_call_operator, + test_stride_with_empty_extents, + }); + return exec.run(argc, argv); +} 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..2997a5f9a73 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_mdspan/test.cpp @@ -0,0 +1,1425 @@ +// 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; + } + + [[nodiscard]] constexpr int get_id() const noexcept { + return id; + } + + [[nodiscard]] constexpr bool is_copy_constructed() const noexcept { + return copy_constructed; + } + + [[nodiscard]] constexpr bool is_move_constructed() const noexcept { + return move_constructed; + } + + [[nodiscard]] constexpr bool is_copy_assigned() const noexcept { + return copy_assigned; + } + + [[nodiscard]] constexpr bool is_move_assigned() const noexcept { + return move_assigned; + } + + [[nodiscard]] 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 : bool { 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 noexcept(noexcept(mp(indices...))) { + 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 noexcept { + 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>); + +template class AccessorTemplate> +constexpr void check_modeled_concepts_and_member_types() { + using Accessor = AccessorTemplate; + using Mds = mdspan; + + // Check modeled concepts + 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) ); + + // Check member types + 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_nothrow_default_constructible_v>>); // strengthened + static_assert(!is_default_constructible_v>>); + static_assert(!is_default_constructible_v>>); + } + + { // Check constraints: 'is_default_constructible_v' + static_assert(is_nothrow_default_constructible_v< + mdspan, layout_right, VectorBoolAccessor>>); // strengthened + 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_nothrow_default_constructible_v< + mdspan, layout_right, AccessorWithCustomOffsetPolicy>>); // strengthened + 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_nothrow_constructible_v); // strengthened + static_assert(is_nothrow_constructible_v); // strengthened + static_assert( + is_nothrow_constructible_v); // strengthened + static_assert(is_nothrow_constructible_v>); // strengthened + static_assert(!is_constructible_v); + } + + { // Check constraint: '(is_nothrow_constructible && ...)' + using Mds = mdspan>; + static_assert(is_nothrow_constructible_v>); // strengthened + static_assert(!is_constructible_v>); + } + + { // Check constraint: 'N == rank() || N == rank_dynamic()' + using Mds = mdspan>; + static_assert(!is_constructible_v); + static_assert(is_nothrow_constructible_v); // strengthened + static_assert(!is_constructible_v); + static_assert(is_nothrow_constructible_v); // strengthened + } + + { // Check constraint: 'is_constructible_v' + static_assert(is_nothrow_constructible_v, layout_left>, int* const, int, + int>); // strengthened + 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_nothrow_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, int, int>); // strengthened + 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_nothrow_constructible_v>); // strengthened + static_assert(is_nothrow_constructible_v>); // strengthened + static_assert(is_nothrow_constructible_v, 3>>); // strengthened + static_assert(is_nothrow_constructible_v, 3>>); // strengthened + static_assert(!is_constructible_v>); + static_assert(!is_constructible_v>); + } + + { // Check constraint: 'is_nothrow_constructible' + using Mds = mdspan>; + static_assert(is_nothrow_constructible_v, 2>>); // strengthened + static_assert(is_nothrow_constructible_v, 2>>); // strengthened + 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_nothrow_constructible_v>); // strengthened + static_assert(is_nothrow_constructible_v>); // strengthened + static_assert(!is_constructible_v>); + static_assert(!is_constructible_v>); + static_assert(is_nothrow_constructible_v>); // strengthened + static_assert(is_nothrow_constructible_v>); // strengthened + } + + { // Check constraint: 'is_constructible_v' + static_assert(is_nothrow_constructible_v, layout_left>, int* const, + span>); // strengthened + static_assert(is_nothrow_constructible_v, layout_left>, int* const, + array>); // strengthened + static_assert( + is_constructible_v, TrackingLayout>, + int* const, span>); + static_assert( + !is_nothrow_constructible_v, TrackingLayout>, + int* const, span>); // strengthened + static_assert( + is_constructible_v, TrackingLayout>, + int* const, array>); + static_assert( + !is_nothrow_constructible_v, TrackingLayout>, + int* const, array>); // strengthened + 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_nothrow_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, span>); // strengthened + static_assert(is_nothrow_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, array>); // strengthened + 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_nothrow_constructible_v>, int*, dextents>); // strengthened + static_assert( + is_nothrow_constructible_v>, int*, dextents>); // strengthened + 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_nothrow_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, dextents>); // strengthened + static_assert(is_nothrow_constructible_v, layout_right, VectorBoolAccessor>, + vector::iterator, extents>); // strengthened + 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_nothrow_constructible_v, layout_left, VectorBoolAccessor>, + vector::iterator, layout_left::mapping>>); // strengthened + static_assert(is_nothrow_constructible_v, layout_left>, int* const, + layout_left::mapping>>); // strengthened + 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() { + { // Check effects + 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}}; + + // 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); + } + + { // Check noexceptness (strengthened) + using Mds1 = mdspan>; + static_assert(is_nothrow_constructible_v); + + using Mds2 = mdspan, TrackingLayout<>>; + static_assert(!is_nothrow_constructible_v); + } +} + +constexpr void check_construction_from_other_mdspan() { + { // Check constraint: 'is_constructible_v&>' + static_assert(is_nothrow_constructible_v, layout_stride>, + mdspan, layout_right>>); // strengthened + 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_nothrow_constructible_v>, + mdspan>>); // strengthened + 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(!CanCallMultidimSubscriptOp); +#endif // __clang__ + } + + { // Check constraint: '(is_nothrow_constructible_v && ...)' + using Mds = mdspan>; + static_assert(CanCallMultidimSubscriptOp, int>); +#ifndef __clang__ // TRANSITION, Clang 17 + static_assert(!CanCallMultidimSubscriptOp, int>); +#endif // __clang__ + } + + { // Check constraint: 'sizeof...(OtherIndexTypes) == rank()' + using Mds = mdspan>; + static_assert(CanCallMultidimSubscriptOp); +#ifndef __clang__ // TRANSITION, Clang 17 + static_assert(!CanCallMultidimSubscriptOp); + static_assert(!CanCallMultidimSubscriptOp); +#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); + +#ifndef __clang__ // TRANSITION, Clang 17 + static_assert(noexcept(mds[1, 1])); // strengthened + static_assert(noexcept(as_const(mds)[0, 1])); // strengthened +#endif // __clang__ + } + + { // 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(const FunkyIndex&) = delete; + + constexpr operator integral auto() const& noexcept { + return 0; + } + + constexpr operator integral auto() && noexcept { + return 0; + } + + constexpr operator Ext::index_type() const& 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() { + { // Check swapping with tracking types + 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)); + } + + { // Check swapping with standard layout and accessor + using Mds = mdspan>; + static_assert(is_nothrow_swappable_v); + static_assert(!is_swappable_v); + + int diag[] = {1, 0, 0, 1}; + Mds mds1{diag}; + int revdiag[] = {0, 1, 1, 0}; + Mds mds2{revdiag}; + + swap(mds1, mds2); + assert(mds1.data_handle() == revdiag); + assert(mds2.data_handle() == diag); + } +} + +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>>); + } +} + +// When +// * 'Mds::accessor_type' is a specialization of 'default_accessor', and +// * 'Mds::layout_type' is +// * 'layout_left' or 'layout_right' and 'Mds::extents_type::rank_dynamic() == 0', or +// * 'layout_stride' and 'Mds::extents_type::rank() == 0' +// then 'sizeof(Mds) == sizeof(void*)' (MSVC STL specific behavior). +static_assert(sizeof(mdspan, layout_left>) == sizeof(void*)); +static_assert(sizeof(mdspan, layout_left>) > sizeof(void*)); +static_assert(sizeof(mdspan, layout_left, TrivialAccessor>) > sizeof(void*)); + +static_assert(sizeof(mdspan, layout_right>) == sizeof(void*)); +static_assert(sizeof(mdspan, layout_right>) > sizeof(void*)); +static_assert(sizeof(mdspan, layout_right, TrivialAccessor>) > sizeof(void*)); + +static_assert(sizeof(mdspan, layout_stride>) == sizeof(void*)); +static_assert(sizeof(mdspan, layout_stride>) > sizeof(void*)); +static_assert(sizeof(mdspan, layout_stride>) > sizeof(void*)); +static_assert(sizeof(mdspan, layout_stride, TrivialAccessor>) > sizeof(void*)); + +constexpr bool test() { + check_modeled_concepts_and_member_types, layout_left, default_accessor>(); + check_modeled_concepts_and_member_types, layout_right, default_accessor>(); + check_modeled_concepts_and_member_types, layout_stride, default_accessor>(); + check_modeled_concepts_and_member_types, layout_left, TrackingAccessor>(); + check_modeled_concepts_and_member_types, layout_stride, TrivialAccessor>(); + check_modeled_concepts_and_member_types, TrackingLayout<>, + AccessorWithTrackingDataHandle>(); + 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(); +} diff --git a/tests/std/tests/P0009R18_mdspan_mdspan_death/env.lst b/tests/std/tests/P0009R18_mdspan_mdspan_death/env.lst new file mode 100644 index 00000000000..18e2d7c71ec --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_mdspan_death/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_death/test.cpp b/tests/std/tests/P0009R18_mdspan_mdspan_death/test.cpp new file mode 100644 index 00000000000..f4c072422c4 --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_mdspan_death/test.cpp @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#define _CONTAINER_DEBUG_LEVEL 1 + +#include +#include + +#include + +using namespace std; + +constexpr array some_ints{}; + +void test_construction_from_other_mdspan() { + mdspan mds1{some_ints.data(), 8, 2, 8}; + // For each rank index r of extents_type, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r) + // must be true + [[maybe_unused]] mdspan> mds2{mds1}; +} + +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 +void test_access_with_invalid_multidimensional_index_1() { + mdspan mds{some_ints.data(), 4, 4}; + // I must be a multidimensional index in extents() + (void) mds[3, 4]; +} +#endif // __cpp_multidimensional_subscript + +void test_access_with_invalid_multidimensional_index_2() { + mdspan mds{some_ints.data(), 5, 5}; + // I must be a multidimensional index in extents() + (void) mds[array{4, 5}]; +} + +void test_size_when_index_type_is_signed() { + mdspan mds{some_ints.data(), dextents{8, 8, 4}}; + // The size of the multidimensional index space extents() must be representable as a value of type size_type + (void) mds.size(); +} + +void test_size_when_index_type_is_unsigned() { + mdspan mds{some_ints.data(), dextents{8, 8, 4}}; + // The size of the multidimensional index space extents() must be representable as a value of type size_type + (void) mds.size(); +} + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + exec.add_death_tests({ + test_construction_from_other_mdspan, +#ifdef __cpp_multidimensional_subscript // TRANSITION, P2128R6 + test_access_with_invalid_multidimensional_index_1, +#endif // __cpp_multidimensional_subscript + test_access_with_invalid_multidimensional_index_2, + test_size_when_index_type_is_signed, + test_size_when_index_type_is_unsigned, + }); + return exec.run(argc, argv); +} diff --git a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc index 70d0af803ec..d4960889f74 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc +++ b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc @@ -40,6 +40,7 @@ "list", "locale", "map", + "mdspan", "memory", "memory_resource", "mutex", diff --git a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp index 66fbc4ea5a1..1617b5e2859 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -48,6 +48,9 @@ import ; import ; import ; import ; +#if TEST_STANDARD >= 23 +import ; +#endif // TEST_STANDARD >= 23 import ; import ; import ; diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index e130dd7f546..2d0fac21e96 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -1492,6 +1492,20 @@ STATIC_ASSERT(__cpp_lib_math_special_functions == 201603L); #endif #endif +#if _HAS_CXX23 && defined(__cpp_lib_concepts) // TRANSITION, GH-395 +#ifndef __cpp_lib_mdspan +#error __cpp_lib_mdspan is not defined +#elif __cpp_lib_mdspan != 202207L +#error __cpp_lib_mdspan is not 202207L +#else +STATIC_ASSERT(__cpp_lib_mdspan == 202207L); +#endif +#else +#ifdef __cpp_lib_mdspan +#error __cpp_lib_mdspan is defined +#endif +#endif + #if _HAS_CXX17 #ifndef __cpp_lib_memory_resource #error __cpp_lib_memory_resource is not defined diff --git a/tests/std/tests/include_each_header_alone_matrix.lst b/tests/std/tests/include_each_header_alone_matrix.lst index 9b041e051e5..4d07f7d8bba 100644 --- a/tests/std/tests/include_each_header_alone_matrix.lst +++ b/tests/std/tests/include_each_header_alone_matrix.lst @@ -44,6 +44,7 @@ PM_CL="/DMEOW_HEADER=limits" PM_CL="/DMEOW_HEADER=list" PM_CL="/DMEOW_HEADER=locale" PM_CL="/DMEOW_HEADER=map" +PM_CL="/DMEOW_HEADER=mdspan" PM_CL="/DMEOW_HEADER=memory" PM_CL="/DMEOW_HEADER=memory_resource" PM_CL="/DMEOW_HEADER=mutex"