[PATCH 2/2] libstdc++: Assert that produced layout_stride::mapping is unique.

Tomasz Kamiński tkaminsk@redhat.com
Tue Jul 7 10:18:32 GMT 2026


This add an assertions checking if the combination of strides
and extents passed to layout_stride::mapping is unique.

To avoid excessive performance impact, in non-debug mode only
checks that are are O(rank) (i.e. similar cost as operator())
are performed. To achieve that, for the ranks greater or equal,
we perform two separate checks:
* required_span_size() > mdspan::__size(_M_extents) - this is
  required by not sufficient condition, that is O(rank) and
  is always performed.
* building an permutation using selection-sort based algorithm,
  this is O(rank^2) and is performed only in debug modes.

The check for non-zero stride values (performed for all non-zero
ranks) is done only if the size of the multidimensional index space
is not empty. This follows the resolution of LWG4603, and provides
consistent behavior for constructing layout_stride mapping from
layout_left/right directly or from strides extracted from it.

libstdc++-v3/ChangeLog:

	* include/std/mdspan (layout_stride::mapping::_M_check_unique):
	Define.
	(layout_stride::mapping(const extents_type&, span<....>):
	Add __glibcxx_assert on _M_check_unique.
	* testsuite/23_containers/mdspan/layouts/stride_neg.cc:
	Test for the new added assertions.
---
Are we OK with full detection only for ranks <= 2 and some subset of
problematic layouts with rank >= 3? I think this is good sweet spot.
But I see how some may argue for consistency here, but then I would 
put unique dection only in debug for all ranks.

Tested on x86_64-linux locally. *mdspan* test passed in all standard
modes and with _GLIBCXX_DEBUG. OK for trunk?

 libstdc++-v3/include/std/mdspan               | 83 ++++++++++++++++++-
 .../mdspan/layouts/stride_neg.cc              | 42 ++++++++++
 2 files changed, 123 insertions(+), 2 deletions(-)

diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan
index 5e1c4d8b1a4..69dbe091884 100644
--- a/libstdc++-v3/include/std/mdspan
+++ b/libstdc++-v3/include/std/mdspan
@@ -1893,6 +1893,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  for (size_t __i = 0; __i < extents_type::rank(); ++__i)
 	    _M_strides[__i]
 	       = __mdspan::__index_type_cast<index_type>(__strides[__i]);
+
+	  __glibcxx_assert(_M_check_unique());
 	}
 
       template<typename _OIndexType>
@@ -2027,6 +2029,85 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 
     private:
+      using _Strides = typename __array_traits<index_type,
+					       extents_type::rank()>::_Type;
+
+      constexpr bool
+      _M_check_unique() const
+      {
+	constexpr size_t __rank = extents_type::rank();
+	if constexpr (__rank == 0)
+	  return true;
+	// _GLIBCXX_RESOLVE_LIB_DEFECTS
+	// 4603. `layout_stride` should accept zero strides for empty `extents`
+	else if (__mdspan::__empty(_M_extents))
+	  return true;
+	else if constexpr (__rank == 1)
+	  return _M_strides[0] > 0;
+	else if (__mdspan::__contains_zero<const index_type, __rank>(_M_strides))
+	  return false;
+	else if constexpr (__rank == 2)
+	  return (_M_strides[1] >= _M_strides[0] * _M_extents.extent(0))
+	      || (_M_strides[0] >= _M_strides[1] * _M_extents.extent(1));
+	else
+	  {
+	    // This is necessary, but not sufficient condition for uniquness.
+	    // It requires computation in range of call of operator(), so check
+	    // if first, also in non-debug mode.
+	    if (required_span_size() < __mdspan::__size(_M_extents))
+	      return false;
+
+	    // Checking if pertmuation of strides exists is O(rank^2) so enabled
+	    // only in debug mode.
+#ifdef _GLIBCXX_DEBUG
+	    _Strides __extents_arr, __strides_arr;
+	    for (size_t __i = 0; __i < __rank; ++__i)
+	      {
+		__strides_arr[__i] = _M_strides[__i];
+	        __extents_arr[__i] = _M_extents.extent(__i);
+	      }
+	    span<index_type> __strides = __strides_arr;
+	    span<index_type> __extents = __extents_arr;
+
+	    auto __popmin = [](span<index_type>& __vals)
+	    {
+	      index_type& __front = __vals.front();
+	      index_type* __min = &__front;
+	      __vals = __vals.subspan(1);
+	      for (index_type& __v : __vals)
+		if (__v < *__min)
+		  __min = &__v;
+
+	      std::swap(__front, *__min);
+	      return __front;
+	    };
+
+	    // We use eager selection-sort based algorithm to permute the __strides.
+	    index_type __prev = __popmin(__strides);
+	    while (!__strides.empty())
+	      {
+		const index_type __next = __popmin(__strides);
+
+		// Find biggest extents that could map to this stride.
+		index_type* __max = nullptr;
+	        for (index_type& __ext : __extents)
+		  if (__next >= __prev * __ext)
+		    if (!__max || __ext > *__max)
+		      __max = &__ext;
+
+		// None of the extents passes check, non-unique layout.
+                if (!__max)
+		  return false;
+
+		std::swap(__extents.front(), *__max);
+	        __extents = __extents.subspan(1);	
+		__prev = __next;
+	      }
+#endif
+	    return true;
+	  }
+      }
+
 #if __glibcxx_submdspan
       template<typename... _Slices>
 	requires (extents_type::rank() == sizeof...(_Slices))
@@ -2047,8 +2128,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif
 
-      using _Strides = typename __array_traits<index_type,
-					       extents_type::rank()>::_Type;
       [[no_unique_address]] extents_type _M_extents;
       [[no_unique_address]] _Strides _M_strides;
     };
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc
index 153560bc82f..96d3c63717a 100644
--- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc
@@ -27,5 +27,47 @@ test_stride_negative()
 }
 static_assert(test_stride_negative()); // { dg-error "expansion of" } 
 
+template<size_t Rank>
+  constexpr bool
+  test_is_unique(const std::array<size_t, Rank>& extents, const std::array<size_t, Rank>& strides)
+  {
+    auto exts = std::dextents<size_t, Rank>(extents);
+    auto m = std::layout_stride::mapping(exts, strides); // { dg-error "expansion of" } 
+    (void) m;
+    return true;
+  }
+
+static_assert(test_is_unique<0>({}, {}));
+static_assert(test_is_unique<1>({0}, {0})); // empty special case
+static_assert(test_is_unique<1>({1}, {0})); // { dg-error "expansion of" }
+
+static_assert(test_is_unique<2>({0, 1}, {0, 0})); // empty special case
+static_assert(test_is_unique<2>({1, 5}, {1, 1}));
+static_assert(test_is_unique<2>({3, 4}, {3, 10}));
+static_assert(test_is_unique<2>({3, 4}, {10, 3}));
+static_assert(test_is_unique<2>({3, 4}, {2, 3})); // { dg-error "expansion of" }
+static_assert(test_is_unique<2>({3, 4}, {1, 2})); // { dg-error "expansion of" }
+
+static_assert(test_is_unique<3>({0, 1, 2}, {0, 0, 0})); // empty special case
+static_assert(test_is_unique<3>({1, 1, 5}, {1, 1, 1}));
+static_assert(test_is_unique<3>({3, 4, 5}, {2, 11, 35}));
+static_assert(test_is_unique<3>({3, 4, 5}, {35, 2, 11}));
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 3, 11})); // { dg-error "expansion of" }
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 2, 12})); // { dg-error "expansion of" }
+// Passes required_span_size check, detected only in debug mode
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 2, 25})); // { dg-error "expansion of" "" { target debug_mode } }
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 5, 12})); // { dg-error "expansion of" "" { target debug_mode } }
+
+static_assert(test_is_unique<4>({0, 1, 2, 3}, {0, 0, 0, 0})); // empty special case
+static_assert(test_is_unique<4>({1, 1, 1, 5}, {1, 1, 1, 1}));
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {3, 16, 65, 200}));
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {200, 65, 3, 16}));
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 3, 12, 50})); // { dg-error "expansion of" }
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 3, 11, 60})); // { dg-error "expansion of" }
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 2, 12, 60})); // { dg-error "expansion of" }
+// Passes required_span_size check, detected only in debug mode
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 2, 12, 100})); // { dg-error "expansion of" "" { target debug_mode } }
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 5, 12, 60}));  // { dg-error "expansion of" "" { target debug_mode } }
+
 // { dg-prune-output "non-constant condition for static assertion" }
 // { dg-prune-output "__glibcxx_assert_fail()" }
-- 
2.54.0



More information about the Libstdc++ mailing list