diff --git a/stl/inc/mdspan b/stl/inc/mdspan index 2fb443e92ec..df756cd9e78 100644 --- a/stl/inc/mdspan +++ b/stl/inc/mdspan @@ -601,9 +601,34 @@ public: "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 (N4944 [mdspan.layout.stride.overview]/4)."); - constexpr mapping() noexcept = default; +#pragma warning(push) // TRANSITION, "/analyze:only" BUG? +#pragma warning(disable : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + constexpr mapping() noexcept : _Exts(extents_type{}) { + if constexpr (extents_type::rank() != 0) { + _Strides.back() = 1; + for (rank_type _Idx = extents_type::rank() - 1; _Idx-- > 0;) { + // TRANSITION USE `_Multiply_with_overflow_check` IN DEBUG MODE + _Strides[_Idx] = _Strides[_Idx + 1] * _Exts.extent(_Idx + 1); + } + } + } +#pragma warning(pop) // TRANSITION, "/analyze:only" BUG? + constexpr mapping(const mapping&) noexcept = default; + template + requires is_convertible_v + && is_nothrow_constructible_v + constexpr mapping(const extents_type& _Exts_, span<_OtherIndexType, extents_type::rank()> _Strides_, + index_sequence<_Indices...>) noexcept + : _Exts(_Exts_), _Strides{static_cast(_STD as_const(_Strides_[_Indices]))...} { + for (rank_type _Idx = 0; _Idx < extents_type::rank(); ++_Idx) { + // TRANSITION CHECK [mdspan.layout.stride.cons]/4.2 (REQUIRES `_Multiply_with_overflow_check`) + _STL_VERIFY(_Strides[_Idx] > 0, "Value of s[i] must be greater than 0 for all i in the range [0, rank_) " + "(N4944 [mdspan.layout.stride.cons]/4.1)."); + } + } + #ifndef __clang__ // TRANSITION, MSVC messes up CTAD when concepts are used here (needs further investigation) template && is_nothrow_constructible_v, @@ -613,12 +638,9 @@ public: requires is_convertible_v && is_nothrow_constructible_v #endif // ^^^ no workaround ^^^ - constexpr mapping(const extents_type& _Exts_, const span<_OtherIndexType, extents_type::rank()> _Strides_) noexcept - : _Exts(_Exts_) { - for (rank_type _Idx = 0; _Idx < extents_type::rank(); ++_Idx) { - _Strides[_Idx] = _Strides_[_Idx]; - } - }; + constexpr mapping(const extents_type& _Exts_, span<_OtherIndexType, extents_type::rank()> _Strides_) noexcept + : mapping(_Exts_, _Strides_, make_index_sequence{}) { + } #ifndef __clang__ // TRANSITION, MSVC messes up CTAD when concepts are used here (needs further investigation) template @@ -631,11 +653,8 @@ public: #endif // ^^^ no workaround ^^^ constexpr mapping( const extents_type& _Exts_, const array<_OtherIndexType, extents_type::rank()>& _Strides_) noexcept - : _Exts(_Exts_) { - for (rank_type _Idx = 0; _Idx < extents_type::rank(); ++_Idx) { - _Strides[_Idx] = _Strides_[_Idx]; - } - }; + : mapping(_Exts_, span{_Strides_}, make_index_sequence{}) { + } template requires _Layout_mapping_alike<_StridedLayoutMapping> @@ -647,8 +666,16 @@ public: || _Is_mapping_of) )) mapping(const _StridedLayoutMapping& _Other) noexcept : _Exts(_Other.extents()) { - for (rank_type _Dim = 0; _Dim < extents_type::rank(); ++_Dim) { - _Strides[_Dim] = _Other.stride(_Dim); + _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 (N4944 " + "[mdspan.layout.stride.cons]/7.3)."); + _STL_VERIFY( + _Offset(_Other) == 0, "Value of OFFSET(other) must be equal to 0 (N4944 [mdspan.layout.stride.cons]/7.4)."); + for (rank_type _Idx = 0; _Idx < extents_type::rank(); ++_Idx) { + const auto _Stride = _Other.stride(_Idx); + _STL_VERIFY(_Stride > 0, "Value of other.stride(r) must be greater than 0 for every rank index r of " + "extents() (N4944 [mdspan.layout.stride.cons]/7.2)."); + _Strides[_Idx] = static_cast(_Stride); } } @@ -662,30 +689,32 @@ public: return _Strides; } +#pragma warning(push) // TRANSITION, "/analyze:only" BUG? +#pragma warning(disable : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call _NODISCARD constexpr index_type required_span_size() const noexcept { - if (extents_type::rank() > 0) { + if constexpr (extents_type::rank() == 0) { + return 1; + } else { index_type _Result = 1; - for (rank_type _Dim = 0; _Dim < extents_type::rank(); ++_Dim) { - const auto _Ext = _Exts.extent(_Dim); + for (rank_type _Idx = 0; _Idx < extents_type::rank(); ++_Idx) { + const index_type _Ext = _Exts.extent(_Idx); if (_Ext == 0) { return 0; } - _Result += (_Ext - 1) * _Strides[_Dim]; + _Result += (_Ext - 1) * _Strides[_Idx]; } return _Result; - } else { - return 1; } } +#pragma warning(pop) // TRANSITION, "/analyze:only" BUG? - template - requires (sizeof...(_Indices) == extents_type::rank()) && (is_convertible_v<_Indices, index_type> && ...) - && (is_nothrow_constructible_v && ...) - _NODISCARD constexpr index_type operator()(_Indices... _Idx) const noexcept { - return _Index_impl...>( - static_cast(_Idx)..., make_index_sequence{}); + 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 { @@ -700,20 +729,19 @@ public: return true; } - _NODISCARD constexpr bool is_unique() const noexcept { + _NODISCARD static constexpr bool is_unique() noexcept { return true; } _NODISCARD constexpr bool is_exhaustive() const noexcept { - index_type _Ext_size = 1; - for (rank_type _Dim = 0; _Dim < extents_type::rank(); ++_Dim) { - _Ext_size *= _Exts.extent(_Dim); + if constexpr (extents_type::rank() == 0) { + return true; + } else { + return required_span_size() == _Exts._Fwd_prod_of_extents(extents_type::rank()); } - - return required_span_size() == _Ext_size; } - _NODISCARD constexpr bool is_strided() const noexcept { + _NODISCARD static constexpr bool is_strided() noexcept { return true; } @@ -725,49 +753,47 @@ public: 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 (_Left.extents() != _Right.extents()) { - return false; - } - - constexpr rank_type _Rank = extents_type::rank(); - for (rank_type _Dim = 0; _Dim < _Rank; ++_Dim) { - if (_Left.stride(_Dim) != _Right.stride(_Dim)) { + if constexpr (extents_type::rank() != 0) { + if (_Left.extents() != _Right.extents()) { return false; } - } - - index_type _Offset; - if constexpr (_Rank == 0) { - _Offset = _Right(); - } else { - bool _Is_empty = false; - for (rank_type _Dim = 0; _Dim < _Rank; ++_Dim) { - if (_Left.extents().extent(_Dim) == 0) { - _Is_empty = true; - break; - } - } - if (_Is_empty) { - _Offset = 0; - } else { - _Offset = [&_Right](index_sequence<_Idx...>) { - return _Right(((void) _Idx, 0)...); + for (rank_type _Idx = 0; _Idx < extents_type::rank(); ++_Idx) { + if (_Left.stride(_Idx) != _Right.stride(_Idx)) { + return false; } - (make_index_sequence<_Rank>{}); } - - return _Offset == 0; } + + return _Offset(_Right) == 0; } private: extents_type _Exts{}; array _Strides{}; - template - constexpr index_type _Index_impl(_IndexType... _Idx, index_sequence<_Seq...>) const noexcept { - return ((_Idx * _Strides[_Seq]) + ...); + template + _NODISCARD static constexpr typename _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(index_sequence<_Seq...>, _IndexTypes... _Indices) const noexcept { + _STL_INTERNAL_STATIC_ASSERT((same_as<_IndexTypes, index_type> && ...)); + return ((_Indices * _Strides[_Seq]) + ... + 0); } }; diff --git a/tests/std/test.lst b/tests/std/test.lst index 88d477d8777..b447d8a54e6 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -238,6 +238,8 @@ 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\P0019R8_atomic_ref tests\P0024R2_parallel_algorithms_adjacent_difference tests\P0024R2_parallel_algorithms_adjacent_find 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..57683fd893c --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include + +#include "test_mdspan_support.hpp" + +using namespace std; + +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); + +#pragma warning(push) // TRANSITION, "/analyze:only" BUG? +#pragma warning(disable : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call + { // Check default and copy constructor + 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 construction from extents_type and array + Mapping m{ext, strs}; + assert(m.extents() == ext); + assert(m.strides() == strs); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_extents_and_array' function [FIXME] + } + + { // Check construction from extents_type and span + using Span = span; + Mapping m{ext, Span{strs}}; + assert(m.extents() == ext); + assert(m.strides() == strs); + static_assert(is_nothrow_constructible_v); + // Other tests are defined in 'check_construction_from_extents_and_array' function [FIXME] + } + + 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 [FIXME] + } + + 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(strs2 == strs); + static_assert(noexcept(m.strides())); + } + + // Function 'required_span_size' is tested in 'check_required_span_size' function[FIXME] + { // Check 'required_span_size' function[FIXME] + 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())); + } + + // 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 [FIXME] + } + + { // Check 'stride' function (intentionally not if constexpr) + for (size_t i = 0; i < strs.size(); ++i) { + same_as decltype(auto) s = m.stride(i); + assert(strs[i] == s); + } + } + + { // Check comparisons + assert(m == m); + assert(!(m != m)); + // Other tests are defined in 'check_comparisons' function [FIXME] + } +#pragma warning(pop) // TRANSITION, "/analyze:only" BUG? +} + +template +constexpr void check_members(extents ext, const array strides) { + do_check_members(ext, strides, make_index_sequence{}); +} + +constexpr bool test() { + check_members(extents{}, array{}); + check_members(extents{}, array{1}); + check_members(extents{3}, array{1, 3}); + // TRANSITION more tests + 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..4d66c20b0ee --- /dev/null +++ b/tests/std/tests/P0009R18_mdspan_layout_stride_death/test.cpp @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +#include + +using namespace std; + +#pragma warning(push) // TRANSITION, "/analyze:only" BUG? +#pragma warning(disable : 28020) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call +void test_construction_from_extents_and_array() { + // 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_span() { + array s{-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{}, span{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}; +} +#pragma warning(pop) // TRANSITION, "/analyze:only" BUG? + +int main(int argc, char* argv[]) { + std_testing::death_test_executive exec; + exec.add_death_tests({ + // TRANSITION more tests + test_construction_from_extents_and_array, + test_construction_from_extents_and_span, + test_construction_from_strided_layout_mapping, + }); + return exec.run(argc, argv); +}