From 624563ffc2f3ca255e3d754d8ced89cca362a17a Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 18 Nov 2025 15:23:52 +0100 Subject: [PATCH 1/7] Implement LWG-4266: `is_always_exhaustive` function --- stl/inc/mdspan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/mdspan b/stl/inc/mdspan index f2f1f05375a..472c027bc3d 100644 --- a/stl/inc/mdspan +++ b/stl/inc/mdspan @@ -998,7 +998,7 @@ public: } _NODISCARD static constexpr bool is_always_exhaustive() noexcept { - return false; + return extents_type::_Rank == 0 || extents_type::_Multidim_index_space_size_is_always_zero; } _NODISCARD static constexpr bool is_always_strided() noexcept { From f730dfa4dbcf2978d31eae830fd190744da2d48b Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 18 Nov 2025 15:25:01 +0100 Subject: [PATCH 2/7] Implement LWG-4266: `is_exhaustive` function --- stl/inc/mdspan | 73 +++++++++++++------------------------------------- 1 file changed, 19 insertions(+), 54 deletions(-) diff --git a/stl/inc/mdspan b/stl/inc/mdspan index 472c027bc3d..f9ceb7ed3e0 100644 --- a/stl/inc/mdspan +++ b/stl/inc/mdspan @@ -316,7 +316,7 @@ public: } template - friend constexpr pair _Count_dynamic_extents_equal_to_zero_or_one( + friend constexpr size_t _Count_dynamic_extents_equal_to_one( const _ExtentsT&) noexcept; // NB: used by 'layout_stride::mapping::is_exhaustive' }; @@ -805,18 +805,9 @@ constexpr size_t _Count_static_extents_equal_to (static_cast(_Extents == _Val) + ... + 0); template -_NODISCARD constexpr pair _Count_dynamic_extents_equal_to_zero_or_one(const _Extents& _Exts) noexcept { +_NODISCARD constexpr size_t _Count_dynamic_extents_equal_to_one(const _Extents& _Exts) noexcept { _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents> && _Extents::rank_dynamic() != 0); - size_t _Zero_extents = 0; - size_t _One_extents = 0; - for (const auto& _Ext : _Exts._Array) { - if (_Ext == 0) { - ++_Zero_extents; - } else if (_Ext == 1) { - ++_One_extents; - } - } - return {_Zero_extents, _One_extents}; + return static_cast(_RANGES count(_Exts._Array, static_cast<_Extents::index_type>(1))); } template @@ -1010,53 +1001,28 @@ public: } _NODISCARD constexpr bool is_exhaustive() const noexcept { - constexpr size_t _Static_zero_extents = _Count_static_extents_equal_to; - if constexpr (extents_type::rank() == 0) { + if constexpr (is_always_exhaustive()) { return true; } else if constexpr (extents_type::rank() == 1) { - return this->_Array[0] == 1; - } else if constexpr (_Static_zero_extents >= 2) { - // Per N5008 [mdspan.layout.stride.obs]/5.2, we are looking for a permutation P of integers in the range - // '[0, rank)' such that 'stride(p[i]) == stride(p[i-1])*extent(p[i-1])' is true for 'i' in the range - // '[1, rank)'. Knowing that at least two extents are equal to zero, we can deduce that such a permutation - // does not exist: - // - Some 'stride(p[j])' would have to be equal to 'stride(p[j-1])*extents(p[j-1]) = stride(p[j-1])*0 = 0' - // which is not possible. - // - Only 'extent(p[rank-1])' can be equal to 0, because it's not required to satisfy the condition above. - // Since we have two or more extents equal to 0 this is not possible either. - return false; + return this->_Array[0] == 1 || this->_Exts.extent(0) == 0; } else if constexpr (extents_type::rank() == 2) { return (this->_Array[0] == 1 && this->_Array[1] == this->_Exts.extent(0)) - || (this->_Array[1] == 1 && this->_Array[0] == this->_Exts.extent(1)); - } else { - // NB: Extents equal to 1 are problematic too - sometimes in such cases even when the mapping is exhaustive + || (this->_Array[1] == 1 && this->_Array[0] == this->_Exts.extent(1)) || this->_Exts.extent(0) == 0 + || this->_Exts.extent(1) == 0; + } else if constexpr (_RANGES count(extents_type::_Static_extents, static_cast(1)) != 0) { + // NB: Extents equal to 1 are problematic - sometimes in such cases even when the mapping is exhaustive // this function should return false. // For example, when the extents are [2, 1, 2] and the strides are [1, 5, 2], the mapping is exhaustive // per N5008 [mdspan.layout.reqmts]/16 but not per N5008 [mdspan.layout.stride.obs]/5.2. - constexpr size_t _Static_zero_or_one_extents = - _Static_zero_extents + _Count_static_extents_equal_to; - - if constexpr (extents_type::rank_dynamic() != 0) { - const auto [_Dynamic_zero_extents, _Dynamic_one_extents] = - _Count_dynamic_extents_equal_to_zero_or_one(this->_Exts); - - const size_t _All_zero_extents = _Static_zero_extents + _Dynamic_zero_extents; - if (_All_zero_extents >= 2) { - return false; - } - - const size_t _All_zero_or_one_extents = - _Static_zero_or_one_extents + _Dynamic_zero_extents + _Dynamic_one_extents; - if (_All_zero_or_one_extents == 0) { - return _Is_exhaustive_common_case(); - } - - return _Is_exhaustive_special_case(); - } else if constexpr (_Static_zero_or_one_extents == 0) { - return _Is_exhaustive_common_case(); - } else { + return _Is_exhaustive_special_case(); + } else if constexpr (extents_type::rank_dynamic() == 0) { + return _Is_exhaustive_common_case(); + } else { + if (_Count_dynamic_extents_equal_to_one(this->_Exts) != 0) { return _Is_exhaustive_special_case(); } + + return _Is_exhaustive_common_case(); } } @@ -1127,7 +1093,8 @@ private: } _NODISCARD constexpr bool _Is_exhaustive_common_case() const noexcept { - return required_span_size() == _Fwd_prod_of_extents::_Calculate(this->_Exts, extents_type::_Rank); + const index_type _Prod = _Fwd_prod_of_extents::_Calculate(this->_Exts, extents_type::_Rank); + return _Prod == required_span_size() || _Prod == 0; } _NODISCARD constexpr bool _Is_exhaustive_special_case() const noexcept { @@ -1135,9 +1102,7 @@ private: for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { rank_type _Ext = static_cast(this->_Exts.extent(_Idx)); if (_Ext == 0) { - // NB: _Ext equal to zero is special - we want it to end up as close to the end of the sorted range as - // possible, so we assign max value of rank_type to it. - _Ext = static_cast(-1); + return true; } _Pairs[_Idx] = {static_cast(this->_Array[_Idx]), _Ext}; From 576dc453d93ad88032dafc98c3e86bd52757eefc Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 24 Jun 2025 16:44:25 +0200 Subject: [PATCH 3/7] Update tests --- .../P0009R18_mdspan_layout_stride/test.cpp | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp b/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp index eebbd167afe..484272c8cc8 100644 --- a/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp +++ b/tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp @@ -199,7 +199,7 @@ constexpr void do_check_members(const extents& ext, { // Check 'is_always_[unique/exhaustive/strided]' functions static_assert(Mapping::is_always_unique()); - static_assert(!Mapping::is_always_exhaustive()); + static_assert(Mapping::is_always_exhaustive() == (Ext::rank() == 0 || ((Extents == 0) || ...))); static_assert(Mapping::is_always_strided()); } @@ -410,7 +410,7 @@ constexpr void check_is_exhaustive() { // rank() is equal to 1 check(extents{}, array{1}, true); - check(dextents{0}, array{2}, false); + check(dextents{0}, array{2}, true); check(extents{}, array{3}, false); check(dextents{2}, array{2}, false); check(extents{}, array{1}, true); @@ -426,14 +426,14 @@ constexpr void check_is_exhaustive() { check(extents{5}, array{1, 8}, false); check(dextents{6, 5}, array{1, 10}, false); check(extents{}, array{3, 1}, true); - check(extents{}, array{6, 2}, false); - check(extents{0}, array{6, 1}, false); - check(extents{3}, array{6, 2}, false); - check(dextents{0, 3}, array{7, 2}, false); - check(extents{}, array{1, 1}, false); - check(extents{0, 0}, array{1, 1}, false); - check(dextents{0, 0}, array{1, 2}, false); - check(extents{0}, array{1, 2}, false); + check(extents{}, array{6, 2}, true); + check(extents{0}, array{6, 1}, true); + check(extents{3}, array{6, 2}, true); + check(dextents{0, 3}, array{7, 2}, true); + check(extents{}, array{1, 1}, true); + check(extents{0, 0}, array{1, 1}, true); + check(dextents{0, 0}, array{1, 2}, true); + check(extents{0}, array{1, 2}, true); // rank() is greater than 2 check(extents{}, array{1, 2, 6}, true); @@ -449,20 +449,20 @@ constexpr void check_is_exhaustive() { // rank() is greater than 2 and some extents are equal to 0 check(extents{}, array{7, 14, 1}, true); check(extents{2}, array{1, 14, 2}, true); - check(extents{0}, array{14, 28, 1}, false); - check(extents{0, 7}, array{1, 2, 2}, false); - check(dextents{2, 0, 7}, array{2, 28, 4}, false); - check(extents{}, array{3, 1, 1}, false); - check(extents{0}, array{1, 5, 1}, false); - check(dextents{5, 0, 0}, array{2, 1, 10}, false); - check(extents{}, array{1, 1, 1}, false); + check(extents{0}, array{14, 28, 1}, true); + check(extents{0, 7}, array{1, 2, 2}, true); + check(dextents{2, 0, 7}, array{2, 28, 4}, true); + check(extents{}, array{3, 1, 1}, true); + check(extents{0}, array{1, 5, 1}, true); + check(dextents{5, 0, 0}, array{2, 1, 10}, true); + check(extents{}, array{1, 1, 1}, true); check(extents{}, array{1, 1, 1}, true); // rank() is greater than 2 - one extent is equal to 0 while others are equal to each other check(extents{}, array{1, 9, 3}, true); check(extents{3}, array{3, 9, 1}, true); - check(extents{0, 3}, array{1, 3, 3}, false); - check(dextents{3, 0, 3}, array{1, 4, 8}, false); + check(extents{0, 3}, array{1, 3, 3}, true); + check(dextents{3, 0, 3}, array{1, 4, 8}, true); check(dextents{0, 1, 1}, array{1, 1, 1}, true); // required_span_size() is equal to 1 From 8de180ae746acb5727f5974a3d98cefca22a606b Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 24 Jun 2025 17:18:57 +0200 Subject: [PATCH 4/7] Update libc++ tests --- tests/libcxx/expected_results.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 343abccc27a..17c3df98c02 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -187,9 +187,6 @@ std/ranges/range.adaptors/range.join/range.join.iterator/arrow.pass.cpp FAIL # If any feature-test macro test is failing, this consolidated test will also fail. std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp FAIL -# libc++ incorrectly implements `layout_stride::mapping::is_exhaustive()` -std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp FAIL - # *** INTERACTIONS WITH MSVC THAT UPSTREAM LIKELY WON'T FIX *** # These tests set an allocator with a max_size() too small to default construct an unordered container @@ -616,6 +613,10 @@ std/utilities/utility/mem.res/mem.res.monotonic.buffer/mem.res.monotonic.buffer. # we'd need to reverse the order of the _Mtx_unlock() and _Cnd_broadcast() calls in xnotify.cpp. std/thread/thread.condition/notify_all_at_thread_exit_lwg3343.pass.cpp SKIPPED +# LWG-4266 "`layout_stride::mapping` should treat empty mappings as exhaustive" +std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp FAIL +std/containers/views/mdspan/layout_stride/properties.pass.cpp FAIL + # *** C1XX COMPILER BUGS *** # DevCom-1436243 VSO-1335743 constexpr new initialized array From ff9db7ce03e7a3561fd96539371f6b9c231ca1bb Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 18 Nov 2025 15:25:28 +0100 Subject: [PATCH 5/7] Remove unnecessary `_Count_static_extents_equal_to` helper --- stl/inc/mdspan | 7 ------- 1 file changed, 7 deletions(-) diff --git a/stl/inc/mdspan b/stl/inc/mdspan index f9ceb7ed3e0..2cbd56e8059 100644 --- a/stl/inc/mdspan +++ b/stl/inc/mdspan @@ -797,13 +797,6 @@ concept _Layout_mapping_alike = requires { bool_constant<_Mp::is_always_unique()>::value; }; -template -constexpr size_t _Count_static_extents_equal_to = 0; - -template -constexpr size_t _Count_static_extents_equal_to, _Val> = - (static_cast(_Extents == _Val) + ... + 0); - template _NODISCARD constexpr size_t _Count_dynamic_extents_equal_to_one(const _Extents& _Exts) noexcept { _STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents> && _Extents::rank_dynamic() != 0); From 4556daf96bc4927954fe9db11ff7f45388172d2c Mon Sep 17 00:00:00 2001 From: Jakub Mazurkiewicz Date: Tue, 24 Jun 2025 16:43:51 +0200 Subject: [PATCH 6/7] Drive-by: Use `index_type` instead of `rank_type` in `_Stride_extent_pair` --- stl/inc/mdspan | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stl/inc/mdspan b/stl/inc/mdspan index 2cbd56e8059..4e890a97fe5 100644 --- a/stl/inc/mdspan +++ b/stl/inc/mdspan @@ -817,7 +817,7 @@ public: private: using _Extents_base = _Maybe_fully_static_extents; using _Strides_base = _Maybe_empty_array; - using _Stride_extent_pair = pair; + using _Stride_extent_pair = pair; static_assert(_Is_extents, "Extents must be a specialization of std::extents (N4950 [mdspan.layout.stride.overview]/2)."); @@ -853,7 +853,7 @@ private: || _Add_overflow(_Req_span_size, _Prod, _Req_span_size); } - _Pairs[_Idx] = {static_cast(_Stride), static_cast(_Ext)}; + _Pairs[_Idx] = {_Stride, _Ext}; } _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)."); @@ -1093,12 +1093,12 @@ private: _NODISCARD constexpr bool _Is_exhaustive_special_case() const noexcept { array<_Stride_extent_pair, extents_type::rank()> _Pairs; for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) { - rank_type _Ext = static_cast(this->_Exts.extent(_Idx)); + const index_type _Ext = this->_Exts.extent(_Idx); if (_Ext == 0) { return true; } - _Pairs[_Idx] = {static_cast(this->_Array[_Idx]), _Ext}; + _Pairs[_Idx] = {this->_Array[_Idx], _Ext}; } _RANGES sort(_Pairs); From d3ded3b7e1df3564c06c457ce7e615e83e8811e9 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 18 Nov 2025 21:49:27 -0800 Subject: [PATCH 7/7] Am I wrong? No, it's the children who are wrong. --- tests/libcxx/expected_results.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 17c3df98c02..865ef5c3b12 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -184,6 +184,10 @@ std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer.value/ctor.ite # libc++ doesn't implement LWG-4112 std/ranges/range.adaptors/range.join/range.join.iterator/arrow.pass.cpp FAIL +# libc++ doesn't implement LWG-4266 "`layout_stride::mapping` should treat empty mappings as exhaustive" +std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp FAIL +std/containers/views/mdspan/layout_stride/properties.pass.cpp FAIL + # If any feature-test macro test is failing, this consolidated test will also fail. std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp FAIL @@ -613,10 +617,6 @@ std/utilities/utility/mem.res/mem.res.monotonic.buffer/mem.res.monotonic.buffer. # we'd need to reverse the order of the _Mtx_unlock() and _Cnd_broadcast() calls in xnotify.cpp. std/thread/thread.condition/notify_all_at_thread_exit_lwg3343.pass.cpp SKIPPED -# LWG-4266 "`layout_stride::mapping` should treat empty mappings as exhaustive" -std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp FAIL -std/containers/views/mdspan/layout_stride/properties.pass.cpp FAIL - # *** C1XX COMPILER BUGS *** # DevCom-1436243 VSO-1335743 constexpr new initialized array