<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Tue, Jul 7, 2026 at 12:21 PM Tomasz Kamiński <<a href="mailto:tkaminsk@redhat.com">tkaminsk@redhat.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">This add an assertions checking if the combination of strides<br>
and extents passed to layout_stride::mapping is unique.<br>
<br>
To avoid excessive performance impact, in non-debug mode only<br>
checks that are are O(rank) (i.e. similar cost as operator())<br>
are performed. To achieve that, for the ranks greater or equal,<br>
we perform two separate checks:<br>
* required_span_size() > mdspan::__size(_M_extents) - this is<br>
  required by not sufficient condition, that is O(rank) and<br>
  is always performed.<br>
* building an permutation using selection-sort based algorithm,<br>
  this is O(rank^2) and is performed only in debug modes.<br>
<br>
The check for non-zero stride values (performed for all non-zero<br>
ranks) is done only if the size of the multidimensional index space<br>
is not empty. This follows the resolution of LWG4603, and provides<br>
consistent behavior for constructing layout_stride mapping from<br>
layout_left/right directly or from strides extracted from it.<br>
<br>
libstdc++-v3/ChangeLog:<br>
<br>
        * include/std/mdspan (layout_stride::mapping::_M_check_unique):<br>
        Define.<br>
        (layout_stride::mapping(const extents_type&, span<....>):<br>
        Add __glibcxx_assert on _M_check_unique.<br>
        * testsuite/23_containers/mdspan/layouts/stride_neg.cc:<br>
        Test for the new added assertions.<br>
---<br>
Are we OK with full detection only for ranks <= 2 and some subset of<br>
problematic layouts with rank >= 3? I think this is good sweet spot.<br>
But I see how some may argue for consistency here, but then I would <br>
put unique dection only in debug for all ranks.<br>
<br>
Tested on x86_64-linux locally. *mdspan* test passed in all standard<br>
modes and with _GLIBCXX_DEBUG. OK for trunk?<br>
<br>
 libstdc++-v3/include/std/mdspan               | 83 ++++++++++++++++++-<br>
 .../mdspan/layouts/stride_neg.cc              | 42 ++++++++++<br>
 2 files changed, 123 insertions(+), 2 deletions(-)<br>
<br>
diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan<br>
index 5e1c4d8b1a4..69dbe091884 100644<br>
--- a/libstdc++-v3/include/std/mdspan<br>
+++ b/libstdc++-v3/include/std/mdspan<br>
@@ -1893,6 +1893,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION<br>
          for (size_t __i = 0; __i < extents_type::rank(); ++__i)<br>
            _M_strides[__i]<br>
               = __mdspan::__index_type_cast<index_type>(__strides[__i]);<br>
+<br>
+         __glibcxx_assert(_M_check_unique());<br>
        }<br>
<br>
       template<typename _OIndexType><br>
@@ -2027,6 +2029,85 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION<br>
        }<br>
<br>
     private:<br>
+      using _Strides = typename __array_traits<index_type,<br>
+                                              extents_type::rank()>::_Type;<br>
+<br>
+      constexpr bool<br>
+      _M_check_unique() const<br>
+      {<br>
+       constexpr size_t __rank = extents_type::rank();<br>
+       if constexpr (__rank == 0)<br>
+         return true;<br>
+       // _GLIBCXX_RESOLVE_LIB_DEFECTS<br>
+       // 4603. `layout_stride` should accept zero strides for empty `extents`<br>
+       else if (__mdspan::__empty(_M_extents))<br>
+         return true;<br>
+       else if constexpr (__rank == 1)<br>
+         return _M_strides[0] > 0;<br>
+       else if (__mdspan::__contains_zero<const index_type, __rank>(_M_strides))<br>
+         return false;<br>
+       else if constexpr (__rank == 2)<br>
+         return (_M_strides[1] >= _M_strides[0] * _M_extents.extent(0))<br>
+             || (_M_strides[0] >= _M_strides[1] * _M_extents.extent(1));<br>
+       else<br>
+         {<br>
+           // This is necessary, but not sufficient condition for uniquness.<br>
+           // It requires computation in range of call of operator(), so check<br>
+           // if first, also in non-debug mode.<br>
+           if (required_span_size() < __mdspan::__size(_M_extents))<br>
+             return false;<br>
+<br>
+           // Checking if pertmuation of strides exists is O(rank^2) so enabled<br>
+           // only in debug mode.<br>
+#ifdef _GLIBCXX_DEBUG<br>
+           _Strides __extents_arr, __strides_arr;<br>
+           for (size_t __i = 0; __i < __rank; ++__i)<br>
+             {<br>
+               __strides_arr[__i] = _M_strides[__i];<br>
+               __extents_arr[__i] = _M_extents.extent(__i);<br>
+             }<br>
+           span<index_type> __strides = __strides_arr;<br>
+           span<index_type> __extents = __extents_arr;<br>
+<br>
+           auto __popmin = [](span<index_type>& __vals)<br>
+           {<br>
+             index_type& __front = __vals.front();<br>
+             index_type* __min = &__front;<br>
+             __vals = __vals.subspan(1);<br>
+             for (index_type& __v : __vals)<br>
+               if (__v < *__min)<br>
+                 __min = &__v;<br>
+<br>
+             std::swap(__front, *__min);<br>
+             return __front;<br>
+           };<br>
+<br>
+           // We use eager selection-sort based algorithm to permute the __strides.<br>
+           index_type __prev = __popmin(__strides);<br>
+           while (!__strides.empty())<br>
+             {<br>
+               const index_type __next = __popmin(__strides);<br>
+<br>
+               // Find biggest extents that could map to this stride.<br></blockquote><div>That should check extents at position where the minimum stride was. Will post v2 later.</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+               index_type* __max = nullptr;<br>
+               for (index_type& __ext : __extents)<br>
+                 if (__next >= __prev * __ext)<br>
+                   if (!__max || __ext > *__max)<br>
+                     __max = &__ext;<br>
+<br>
+               // None of the extents passes check, non-unique layout.<br>
+                if (!__max)<br>
+                 return false;<br>
+<br>
+               std::swap(__extents.front(), *__max);<br>
+               __extents = __extents.subspan(1);       <br>
+               __prev = __next;<br>
+             }<br>
+#endif<br>
+           return true;<br>
+         }<br>
+      }<br>
+<br>
 #if __glibcxx_submdspan<br>
       template<typename... _Slices><br>
        requires (extents_type::rank() == sizeof...(_Slices))<br>
@@ -2047,8 +2128,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION<br>
        }<br>
 #endif<br>
<br>
-      using _Strides = typename __array_traits<index_type,<br>
-                                              extents_type::rank()>::_Type;<br>
       [[no_unique_address]] extents_type _M_extents;<br>
       [[no_unique_address]] _Strides _M_strides;<br>
     };<br>
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc<br>
index 153560bc82f..96d3c63717a 100644<br>
--- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc<br>
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc<br>
@@ -27,5 +27,47 @@ test_stride_negative()<br>
 }<br>
 static_assert(test_stride_negative()); // { dg-error "expansion of" } <br>
<br>
+template<size_t Rank><br>
+  constexpr bool<br>
+  test_is_unique(const std::array<size_t, Rank>& extents, const std::array<size_t, Rank>& strides)<br>
+  {<br>
+    auto exts = std::dextents<size_t, Rank>(extents);<br>
+    auto m = std::layout_stride::mapping(exts, strides); // { dg-error "expansion of" } <br>
+    (void) m;<br>
+    return true;<br>
+  }<br>
+<br>
+static_assert(test_is_unique<0>({}, {}));<br>
+static_assert(test_is_unique<1>({0}, {0})); // empty special case<br>
+static_assert(test_is_unique<1>({1}, {0})); // { dg-error "expansion of" }<br>
+<br>
+static_assert(test_is_unique<2>({0, 1}, {0, 0})); // empty special case<br>
+static_assert(test_is_unique<2>({1, 5}, {1, 1}));<br>
+static_assert(test_is_unique<2>({3, 4}, {3, 10}));<br>
+static_assert(test_is_unique<2>({3, 4}, {10, 3}));<br>
+static_assert(test_is_unique<2>({3, 4}, {2, 3})); // { dg-error "expansion of" }<br>
+static_assert(test_is_unique<2>({3, 4}, {1, 2})); // { dg-error "expansion of" }<br>
+<br>
+static_assert(test_is_unique<3>({0, 1, 2}, {0, 0, 0})); // empty special case<br>
+static_assert(test_is_unique<3>({1, 1, 5}, {1, 1, 1}));<br>
+static_assert(test_is_unique<3>({3, 4, 5}, {2, 11, 35}));<br></blockquote><div>This one is not unique, as 4*11 > 35; </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+static_assert(test_is_unique<3>({3, 4, 5}, {35, 2, 11}));<br></blockquote><div>Same here, {11, 2, 35} or {11, 35, 2} are.</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 3, 11})); // { dg-error "expansion of" }<br>
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 2, 12})); // { dg-error "expansion of" }<br>
+// Passes required_span_size check, detected only in debug mode<br>
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 2, 25})); // { dg-error "expansion of" "" { target debug_mode } }<br>
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 5, 12})); // { dg-error "expansion of" "" { target debug_mode } }<br>
+<br>
+static_assert(test_is_unique<4>({0, 1, 2, 3}, {0, 0, 0, 0})); // empty special case<br>
+static_assert(test_is_unique<4>({1, 1, 1, 5}, {1, 1, 1, 1}));<br>
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {3, 16, 65, 200}));<br>
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {200, 65, 3, 16}));<br>
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 3, 12, 50})); // { dg-error "expansion of" }<br>
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 3, 11, 60})); // { dg-error "expansion of" }<br>
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 2, 12, 60})); // { dg-error "expansion of" }<br>
+// Passes required_span_size check, detected only in debug mode<br>
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 2, 12, 100})); // { dg-error "expansion of" "" { target debug_mode } }<br>
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 5, 12, 60}));  // { dg-error "expansion of" "" { target debug_mode } }<br>
+<br>
 // { dg-prune-output "non-constant condition for static assertion" }<br>
 // { dg-prune-output "__glibcxx_assert_fail()" }<br>
-- <br>
2.54.0<br>
<br>
</blockquote></div></div>