[libcxx-commits] [libcxx] c91f835 - [libc++][mdspan] Fix `mdspan::operator[]` bounds checking and implement LWG4020 (#192269)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue May 5 00:52:38 PDT 2026


Author: eiytoq
Date: 2026-05-05T15:52:33+08:00
New Revision: c91f835d7a87f13eff2b715aa1171f5094f0a20a

URL: https://github.com/llvm/llvm-project/commit/c91f835d7a87f13eff2b715aa1171f5094f0a20a
DIFF: https://github.com/llvm/llvm-project/commit/c91f835d7a87f13eff2b715aa1171f5094f0a20a.diff

LOG: [libc++][mdspan] Fix `mdspan::operator[]` bounds checking and implement LWG4020 (#192269)

This example fails to compile with libc++ in hardened mode without this
fix.
```c++
#include <mdspan>

struct RValueInt {
  constexpr operator int() && noexcept { return 0; }
};

int main() {
  int data[1] = {42};
  std::mdspan m(data, std::extents<int, 1>{1});

  m[RValueInt{}];
}
```

Fixes: #171311

Added: 
    

Modified: 
    libcxx/docs/Status/Cxx2cIssues.csv
    libcxx/include/__mdspan/extents.h
    libcxx/include/__mdspan/mdspan.h
    libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.index_operator.pass.cpp
    libcxx/test/std/containers/views/mdspan/mdspan/index_operator.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index e825e9633bdb9..d7f75380cb93e 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -152,7 +152,7 @@
 "`LWG3454 <https://wg21.link/LWG3454>`__","``pointer_traits::pointer_to`` should be ``constexpr``","2025-11 (Kona)","","","`#171307 <https://github.com/llvm/llvm-project/issues/171307>`__",""
 "`LWG3627 <https://wg21.link/LWG3627>`__","Inconsistent specifications for ``std::make_optional`` overloads","2025-11 (Kona)","|Complete|","22","`#171309 <https://github.com/llvm/llvm-project/issues/171309>`__",""
 "`LWG4015 <https://wg21.link/LWG4015>`__","LWG 3973 broke const overloads of ``std::optional`` monadic operations","2025-11 (Kona)","","","`#171310 <https://github.com/llvm/llvm-project/issues/171310>`__",""
-"`LWG4020 <https://wg21.link/LWG4020>`__","``extents::index-cast`` weirdness","2025-11 (Kona)","","","`#171311 <https://github.com/llvm/llvm-project/issues/171311>`__",""
+"`LWG4020 <https://wg21.link/LWG4020>`__","``extents::index-cast`` weirdness","2025-11 (Kona)","|Complete|","23","`#171311 <https://github.com/llvm/llvm-project/issues/171311>`__",""
 "`LWG4136 <https://wg21.link/LWG4136>`__","Specify behavior of [linalg] Hermitian algorithms on diagonal with nonzero imaginary part","2025-11 (Kona)","","","`#171312 <https://github.com/llvm/llvm-project/issues/171312>`__",""
 "`LWG4137 <https://wg21.link/LWG4137>`__","Fix *Mandates*, *Preconditions*, and *Complexity* elements of [linalg] algorithms","2025-11 (Kona)","","","`#171313 <https://github.com/llvm/llvm-project/issues/171313>`__",""
 "`LWG4166 <https://wg21.link/LWG4166>`__","``concat_view::end()`` should be more constrained in order to support noncopyable iterators","2025-11 (Kona)","","","`#171314 <https://github.com/llvm/llvm-project/issues/171314>`__",""

diff  --git a/libcxx/include/__mdspan/extents.h b/libcxx/include/__mdspan/extents.h
index d16bbd2af44f1..379c762ace3ed 100644
--- a/libcxx/include/__mdspan/extents.h
+++ b/libcxx/include/__mdspan/extents.h
@@ -21,12 +21,15 @@
 #include <__config>
 
 #include <__concepts/arithmetic.h>
+#include <__concepts/same_as.h>
 #include <__type_traits/common_type.h>
 #include <__type_traits/integer_traits.h>
 #include <__type_traits/is_convertible.h>
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/is_signed.h>
 #include <__type_traits/make_unsigned.h>
+#include <__type_traits/remove_cvref.h>
+#include <__utility/forward.h>
 #include <__utility/integer_sequence.h>
 #include <__utility/unreachable.h>
 #include <array>
@@ -433,6 +436,15 @@ class extents {
     }
     return true;
   }
+
+  template <class _OtherIndexType>
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto __index_cast(_OtherIndexType&& __i) noexcept {
+    using _OtherIndex = remove_cvref_t<_OtherIndexType>;
+    if constexpr (integral<_OtherIndex> && !same_as<_OtherIndex, bool>)
+      return __i;
+    else
+      return static_cast<index_type>(std::forward<_OtherIndexType>(__i));
+  }
 };
 
 // Recursive helper classes to implement dextents alias for extents

diff  --git a/libcxx/include/__mdspan/mdspan.h b/libcxx/include/__mdspan/mdspan.h
index 54685b56a76ff..7f577953ee7ec 100644
--- a/libcxx/include/__mdspan/mdspan.h
+++ b/libcxx/include/__mdspan/mdspan.h
@@ -191,11 +191,11 @@ class mdspan {
              (is_nothrow_constructible_v<index_type, _OtherIndexTypes> && ...) &&
              (sizeof...(_OtherIndexTypes) == rank()))
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr reference operator[](_OtherIndexTypes... __indices) const {
-    // Note the standard layouts would also check this, but user provided ones may not, so we
-    // check the precondition here
-    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__mdspan_detail::__is_multidimensional_index_in(extents(), __indices...),
-                                        "mdspan: operator[] out of bounds access");
-    return __acc_.access(__ptr_, __map_(static_cast<index_type>(std::move(__indices))...));
+    return [&]<class... _IndexTypes>(_IndexTypes... __idxs) -> reference {
+      _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__mdspan_detail::__is_multidimensional_index_in(extents(), __idxs...),
+                                          "mdspan: operator[] out of bounds access");
+      return __acc_.access(__ptr_, __map_(static_cast<index_type>(std::move(__idxs))...));
+    }(extents_type::__index_cast(std::move(__indices))...);
   }
 
   template <class _OtherIndexType>

diff  --git a/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.index_operator.pass.cpp b/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.index_operator.pass.cpp
index 9dd957986f14d..fa3429dc4c4ff 100644
--- a/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.index_operator.pass.cpp
+++ b/libcxx/test/libcxx/containers/views/mdspan/mdspan/assert.index_operator.pass.cpp
@@ -41,6 +41,7 @@ int main(int, char**) {
     TEST_LIBCPP_ASSERT_FAILURE(m[-1], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[-130], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[5], "mdspan: operator[] out of bounds access");
+    TEST_LIBCPP_ASSERT_FAILURE(m[256], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[1000], "mdspan: operator[] out of bounds access");
   }
   {
@@ -48,6 +49,7 @@ int main(int, char**) {
     TEST_LIBCPP_ASSERT_FAILURE(m[-1], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[-130], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[5], "mdspan: operator[] out of bounds access");
+    TEST_LIBCPP_ASSERT_FAILURE(m[128], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[1000], "mdspan: operator[] out of bounds access");
   }
   {
@@ -55,6 +57,7 @@ int main(int, char**) {
     TEST_LIBCPP_ASSERT_FAILURE(m[-1], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[-130], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[5], "mdspan: operator[] out of bounds access");
+    TEST_LIBCPP_ASSERT_FAILURE(m[256], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[1000], "mdspan: operator[] out of bounds access");
   }
   {
@@ -62,6 +65,7 @@ int main(int, char**) {
     TEST_LIBCPP_ASSERT_FAILURE(m[-1], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[-130], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[5], "mdspan: operator[] out of bounds access");
+    TEST_LIBCPP_ASSERT_FAILURE(m[128], "mdspan: operator[] out of bounds access");
     TEST_LIBCPP_ASSERT_FAILURE(m[1000], "mdspan: operator[] out of bounds access");
   }
   {

diff  --git a/libcxx/test/std/containers/views/mdspan/mdspan/index_operator.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/index_operator.pass.cpp
index c26933e80bfaf..3f20bc886d106 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/index_operator.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/index_operator.pass.cpp
@@ -200,10 +200,19 @@ constexpr void test_layout_large() {
 // mapping requirements only require the index operator to mixed integer types not anything convertible to index_type
 constexpr void test_index_cast_happens() {}
 
+struct RValueInt {
+  constexpr operator int() && noexcept { return 0; }
+};
+
 constexpr bool test() {
   test_layout<std::layout_left>();
   test_layout<std::layout_right>();
   test_layout<layout_wrapping_integral<4>>();
+
+  int data[1]{};
+  std::mdspan m(data, std::extents<int, 1>{1});
+  TEST_IGNORE_NODISCARD m[RValueInt{}];
+
   return true;
 }
 


        


More information about the libcxx-commits mailing list