Skip to content

Commit 5e8755d

Browse files
1 parent c764db6 commit 5e8755d

File tree

3 files changed

+185
-21
lines changed

3 files changed

+185
-21
lines changed

stl/inc/mdspan

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#if !_HAS_CXX23
1111
_EMIT_STL_WARNING(STL4038, "The contents of <mdspan> are available only with C++23 or later.");
1212
#else // ^^^ !_HAS_CXX23 / _HAS_CXX23 vvv
13+
#include <algorithm>
1314
#include <array>
1415
#include <span>
1516
#include <tuple>
@@ -311,6 +312,10 @@ public:
311312
return ((0 <= _Indices && _Indices < extent(_Seq)) && ...);
312313
}
313314
}
315+
316+
template <class _ExtentsT>
317+
friend constexpr pair<size_t, size_t> _Count_dynamic_extents_equal_to_zero_or_one(
318+
const _ExtentsT&) noexcept; // NB: used by 'layout_stride::mapping<E>::is_exhaustive'
314319
};
315320

316321
template <class>
@@ -788,6 +793,28 @@ concept _Layout_mapping_alike = requires {
788793
bool_constant<_Mp::is_always_unique()>::value;
789794
};
790795

796+
template <class _Extents, size_t _Val>
797+
constexpr size_t _Count_static_extents_equal_to = 0;
798+
799+
template <class _IndexType, size_t... _Extents, size_t _Val>
800+
constexpr size_t _Count_static_extents_equal_to<extents<_IndexType, _Extents...>, _Val> =
801+
(static_cast<size_t>(_Extents == _Val) + ... + 0);
802+
803+
template <class _Extents>
804+
_NODISCARD constexpr pair<size_t, size_t> _Count_dynamic_extents_equal_to_zero_or_one(const _Extents& _Exts) noexcept {
805+
_STL_INTERNAL_STATIC_ASSERT(_Is_extents<_Extents> && _Extents::rank_dynamic() != 0);
806+
size_t _Zero_extents = 0;
807+
size_t _One_extents = 0;
808+
for (const auto& _Ext : _Exts._Array) {
809+
if (_Ext == 0) {
810+
++_Zero_extents;
811+
} else if (_Ext == 1) {
812+
++_One_extents;
813+
}
814+
}
815+
return {_Zero_extents, _One_extents};
816+
}
817+
791818
template <class _Extents>
792819
class layout_stride::mapping : private _Maybe_fully_static_extents<_Extents>,
793820
private _Maybe_empty_array<typename _Extents::index_type, _Extents::rank()> {
@@ -963,11 +990,53 @@ public:
963990
}
964991

965992
_NODISCARD constexpr bool is_exhaustive() const noexcept {
993+
constexpr size_t _Static_zero_extents = _Count_static_extents_equal_to<extents_type, 0>;
966994
if constexpr (extents_type::rank() == 0) {
967995
return true;
996+
} else if constexpr (extents_type::rank() == 1) {
997+
return this->_Array[0] == 1;
998+
} else if constexpr (_Static_zero_extents >= 2) {
999+
// Per N5008 [mdspan.layout.stride.obs]/5.2, we are looking for a permutation P of integers in the range
1000+
// '[0, rank)' such that 'stride(p[i]) == stride(p[i-1])*extent(p[i-1])' is true for 'i' in the range
1001+
// '[1, rank)'. Knowing that at least two extents are equal to zero, we can deduce that such a permutation
1002+
// does not exist:
1003+
// - Some 'stride(p[j])' would have to be equal to 'stride(p[j-1])*extents(p[j-1]) = stride(p[j-1])*0 = 0'
1004+
// which is not possible.
1005+
// - Only 'extent(p[rank-1])' can be equal to 0, because it's not required to satisfy the condition above.
1006+
// Since we have two or more extents equal to 0 this is not possible either.
1007+
return false;
1008+
} else if constexpr (extents_type::rank() == 2) {
1009+
return (this->_Array[0] == 1 && this->_Array[1] == this->_Exts.extent(0))
1010+
|| (this->_Array[1] == 1 && this->_Array[0] == this->_Exts.extent(1));
9681011
} else {
969-
return required_span_size()
970-
== _Fwd_prod_of_extents<extents_type>::_Calculate(this->_Exts, extents_type::_Rank);
1012+
// NB: Extents equal to 1 are problematic too - sometimes in such cases even when the mapping is exhaustive
1013+
// this function should return false.
1014+
// For example, when the extents are [2, 1, 2] and the strides are [1, 5, 2], the mapping is exhaustive
1015+
// per N5008 [mdspan.layout.reqmts]/16 but not per N5008 [mdspan.layout.stride.obs]/5.2.
1016+
constexpr size_t _Static_zero_or_one_extents =
1017+
_Static_zero_extents + _Count_static_extents_equal_to<extents_type, 1>;
1018+
1019+
if constexpr (extents_type::rank_dynamic() != 0) {
1020+
const auto [_Dynamic_zero_extents, _Dynamic_one_extents] =
1021+
_Count_dynamic_extents_equal_to_zero_or_one(this->_Exts);
1022+
1023+
const size_t _All_zero_extents = _Static_zero_extents + _Dynamic_zero_extents;
1024+
if (_All_zero_extents >= 2) {
1025+
return false;
1026+
}
1027+
1028+
const size_t _All_zero_or_one_extents =
1029+
_Static_zero_or_one_extents + _Dynamic_zero_extents + _Dynamic_one_extents;
1030+
if (_All_zero_or_one_extents == 0) {
1031+
return _Is_exhaustive_common_case();
1032+
}
1033+
1034+
return _Is_exhaustive_special_case();
1035+
} else if constexpr (_Static_zero_or_one_extents == 0) {
1036+
return _Is_exhaustive_common_case();
1037+
} else {
1038+
return _Is_exhaustive_special_case();
1039+
}
9711040
}
9721041
}
9731042

@@ -1036,6 +1105,38 @@ private:
10361105

10371106
return static_cast<index_type>(((_Indices * this->_Array[_Seq]) + ... + 0));
10381107
}
1108+
1109+
_NODISCARD constexpr bool _Is_exhaustive_common_case() const noexcept {
1110+
return required_span_size() == _Fwd_prod_of_extents<extents_type>::_Calculate(this->_Exts, extents_type::_Rank);
1111+
}
1112+
1113+
_NODISCARD constexpr bool _Is_exhaustive_special_case() const noexcept {
1114+
using _Stride_extent_pair = pair<rank_type, rank_type>;
1115+
array<_Stride_extent_pair, extents_type::rank()> _Pairs;
1116+
for (rank_type _Idx = 0; _Idx < extents_type::_Rank; ++_Idx) {
1117+
rank_type _Ext = static_cast<rank_type>(this->_Exts.extent(_Idx));
1118+
if (_Ext == 0) {
1119+
// NB: _Ext equal to zero is special - we want it to end up as close to the end of the sorted range as
1120+
// possible, so we assign max value of rank_type to it.
1121+
_Ext = static_cast<rank_type>(-1);
1122+
}
1123+
1124+
_Pairs[_Idx] = {static_cast<rank_type>(this->_Array[_Idx]), _Ext};
1125+
}
1126+
1127+
_RANGES sort(_Pairs);
1128+
if (_Pairs[0].first != 1) {
1129+
return false;
1130+
}
1131+
1132+
for (rank_type _Idx = 1; _Idx < extents_type::_Rank; ++_Idx) {
1133+
if (_Pairs[_Idx].first != _Pairs[_Idx - 1].first * _Pairs[_Idx - 1].second) {
1134+
return false;
1135+
}
1136+
}
1137+
1138+
return true;
1139+
}
10391140
};
10401141

10411142
_EXPORT_STD template <class _ElementType>

tests/libcxx/expected_results.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ std/ranges/range.adaptors/range.join/range.join.iterator/arrow.pass.cpp FAIL
177177
# If any feature-test macro test is failing, this consolidated test will also fail.
178178
std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp FAIL
179179

180+
# libc++ incorrectly implements `layout_stride::mapping<E>::is_exhaustive()`
181+
std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp FAIL
182+
180183

181184
# *** INTERACTIONS WITH MSVC THAT UPSTREAM LIKELY WON'T FIX ***
182185
# These tests set an allocator with a max_size() too small to default construct an unordered container
@@ -918,9 +921,6 @@ std/time/time.syn/formatter.month_day_last.pass.cpp FAIL
918921
# Our monotonic_buffer_resource takes "user" space for metadata, which it probably should not do.
919922
std/utilities/utility/mem.res/mem.res.monotonic.buffer/mem.res.monotonic.buffer.mem/allocate_with_initial_size.pass.cpp FAIL
920923

921-
# Likely STL bug in layout_stride::mapping::is_exhaustive().
922-
std/containers/views/mdspan/layout_stride/is_exhaustive_corner_case.pass.cpp FAIL
923-
924924

925925
# *** NOT YET ANALYZED ***
926926
# Not analyzed. Clang instantiates BoomOnAnything during template argument substitution.

tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -400,22 +400,85 @@ constexpr void check_required_span_size() {
400400
}
401401

402402
constexpr void check_is_exhaustive() {
403-
{ // Check exhaustive mappings (all possibilities)
404-
using E = extents<int, 2, 3, 5>;
405-
assert((layout_stride::mapping<E>{E{}, array{1, 2, 6}}.is_exhaustive()));
406-
assert((layout_stride::mapping<E>{E{}, array{1, 10, 2}}.is_exhaustive()));
407-
assert((layout_stride::mapping<E>{E{}, array{3, 1, 6}}.is_exhaustive()));
408-
assert((layout_stride::mapping<E>{E{}, array{15, 1, 3}}.is_exhaustive()));
409-
assert((layout_stride::mapping<E>{E{}, array{5, 10, 1}}.is_exhaustive()));
410-
assert((layout_stride::mapping<E>{E{}, array{15, 5, 1}}.is_exhaustive()));
411-
}
412-
413-
{ // Check non-exhaustive mappings
414-
using E = extents<int, 2, 5, 8>;
415-
assert((!layout_stride::mapping<E>{E{}, array{1, 2, 12}}.is_exhaustive()));
416-
assert((!layout_stride::mapping<E>{E{}, array{8, 18, 1}}.is_exhaustive()));
417-
assert((!layout_stride::mapping<E>{E{}, array{5, 1, 12}}.is_exhaustive()));
418-
}
403+
auto check = [](const auto& exts, const auto& strides, bool expected) {
404+
layout_stride::mapping m{exts, strides};
405+
assert(m.is_exhaustive() == expected);
406+
};
407+
408+
// rank() is equal to 0
409+
check(extents<int>{}, array<int, 0>{}, true);
410+
411+
// rank() is equal to 1
412+
check(extents<int, 0>{}, array{1}, true);
413+
check(dextents<int, 1>{0}, array{2}, false);
414+
check(extents<int, 1>{}, array{3}, false);
415+
check(dextents<int, 1>{2}, array{2}, false);
416+
check(extents<int, 3>{}, array{1}, true);
417+
check(dextents<int, 1>{4}, array{1}, true);
418+
419+
// rank() is equal to 2
420+
check(extents<int, 3, 3>{}, array{1, 3}, true);
421+
check(extents<int, dynamic_extent, 3>{3}, array{3, 1}, true);
422+
check(extents<int, 3, dynamic_extent>{3}, array{4, 1}, false);
423+
check(dextents<int, 2>{3, 3}, array{3, 1}, true);
424+
check(extents<int, 4, 5>{}, array{5, 1}, true);
425+
check(extents<int, 6, dynamic_extent>{5}, array{1, 6}, true);
426+
check(extents<int, dynamic_extent, 7>{5}, array{1, 8}, false);
427+
check(dextents<int, 2>{6, 5}, array{1, 10}, false);
428+
check(extents<int, 0, 3>{}, array{3, 1}, true);
429+
check(extents<int, 0, 3>{}, array{6, 2}, false);
430+
check(extents<int, dynamic_extent, 3>{0}, array{6, 1}, false);
431+
check(extents<int, 0, dynamic_extent>{3}, array{6, 2}, false);
432+
check(dextents<int, 2>{0, 3}, array{7, 2}, false);
433+
check(extents<int, 0, 0>{}, array{1, 1}, false);
434+
check(extents<int, 0, dynamic_extent>{0, 0}, array{1, 1}, false);
435+
check(dextents<int, 2>{0, 0}, array{1, 2}, false);
436+
check(extents<int, 1, dynamic_extent>{0}, array{1, 2}, false);
437+
438+
// rank() is greater than 2
439+
check(extents<int, 2, 3, 5>{}, array{1, 2, 6}, true);
440+
check(extents<int, dynamic_extent, 3, 5>{2}, array{1, 10, 2}, true);
441+
check(extents<int, 2, 3, dynamic_extent>{5}, array{3, 1, 6}, true);
442+
check(extents<int, dynamic_extent, dynamic_extent, 5>{2, 3}, array{15, 1, 3}, true);
443+
check(extents<int, 2, dynamic_extent, dynamic_extent>{3, 5}, array{5, 10, 1}, true);
444+
check(dextents<int, 3>{2, 3, 5}, array{15, 5, 1}, true);
445+
check(extents<int, 2, 5, 8>{}, array{1, 2, 12}, false);
446+
check(extents<int, 2, dynamic_extent, 8>{5}, array{8, 18, 1}, false);
447+
check(dextents<int, 3>{2, 5, 8}, array{5, 1, 12}, false);
448+
449+
// rank() is greater than 2 and some extents are equal to 0
450+
check(extents<int, 2, 0, 7>{}, array{7, 14, 1}, true);
451+
check(extents<int, dynamic_extent, 0, 7>{2}, array{1, 14, 2}, true);
452+
check(extents<int, 2, dynamic_extent, 7>{0}, array{14, 28, 1}, false);
453+
check(extents<int, 2, dynamic_extent, dynamic_extent>{0, 7}, array{1, 2, 2}, false);
454+
check(dextents<int, 3>{2, 0, 7}, array{2, 28, 4}, false);
455+
check(extents<int, 5, 0, 0>{}, array{3, 1, 1}, false);
456+
check(extents<int, 5, dynamic_extent, 0>{0}, array{1, 5, 1}, false);
457+
check(dextents<int, 3>{5, 0, 0}, array{2, 1, 10}, false);
458+
check(extents<int, 0, 0, 0>{}, array{1, 1, 1}, false);
459+
check(extents<int, 0, 1, 1>{}, array{1, 1, 1}, true);
460+
461+
// rank() is greater than 2 - one extent is equal to 0 while others are equal to each other
462+
check(extents<int, 3, 0, 3>{}, array{1, 9, 3}, true);
463+
check(extents<int, dynamic_extent, 0, 3>{3}, array{3, 9, 1}, true);
464+
check(extents<int, 3, dynamic_extent, dynamic_extent>{0, 3}, array{1, 3, 3}, false);
465+
check(dextents<int, 3>{3, 0, 3}, array{1, 4, 8}, false);
466+
check(dextents<int, 3>{0, 1, 1}, array{1, 1, 1}, true);
467+
468+
// required_span_size() is equal to 1
469+
check(extents<int, 1>{}, array{1}, true);
470+
check(dextents<int, 1>{1}, array{3}, false);
471+
check(extents<int, 1, dynamic_extent>{1}, array{1, 1}, true);
472+
check(extents<int, 1, 1, 1>{}, array{1, 2, 1}, false);
473+
474+
// Mapping is exhaustive, but is_exhaustive() should return false because of the way standard defined this function
475+
check(extents<int, 3, 1>{}, array{1, 4}, false);
476+
check(dextents<int, 3>{5, 1, 2}, array{2, 11, 1}, false);
477+
check(dextents<int, 3>{2, 3, 1}, array{3, 1, 8}, false);
478+
check(extents<int, 1, dynamic_extent, 7>{6}, array{50, 7, 1}, false);
479+
check(dextents<int, 2>{1, 2}, array{5, 1}, false);
480+
check(extents<int, 6, 1>{}, array{1, 10}, false);
481+
check(dextents<int, 3>{2, 1, 2}, array{3, 3, 1}, false);
419482
}
420483

421484
constexpr void check_call_operator() {

0 commit comments

Comments
 (0)