[libcxx-commits] [libcxx] [libc++] Backport P3029R1 Better `mdspan`'s CTAD to C++20/23 (PR #208132)

A. Jiang via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jul 8 02:25:07 PDT 2026


https://github.com/frederick-vs-ja updated https://github.com/llvm/llvm-project/pull/208132

>From 90b9f5ada6e9997bb10d70c3d051796134431fd1 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Wed, 8 Jul 2026 10:14:16 +0800
Subject: [PATCH 1/2] [libc++] Backport P3029R1 Better `mdspan`'s CTAD to
 C++20/23

Also backport
- support for `T::value` of a reference type in `integral-constant-like`
from P2781R9 `std::constant_wrapper`, and
- LWG4351 _`integral-constant-like`_ needs more `remove_cvref_t`.

P3029R1 (and its further fixes) should be treated as a Defect Report
against C++20/23 to avoid behavior difference in different modes. Other
implementations will do or are already doing the same.
---
 libcxx/docs/Status/Cxx26Papers.csv            |  2 +-
 libcxx/include/__mdspan/extents.h             |  6 -----
 libcxx/include/__mdspan/mdspan.h              |  7 -----
 libcxx/include/mdspan                         |  6 +----
 libcxx/include/span                           | 15 +++--------
 .../views/mdspan/extents/ctad.pass.cpp        | 13 ++++++---
 .../views/mdspan/mdspan/deduction.pass.cpp    | 19 ++++++++-----
 .../views.span/span.cons/deduct.pass.cpp      | 27 ++++++++++++++-----
 8 files changed, 48 insertions(+), 47 deletions(-)

diff --git a/libcxx/docs/Status/Cxx26Papers.csv b/libcxx/docs/Status/Cxx26Papers.csv
index efc98495d1d5c..e1fcaf907c95b 100644
--- a/libcxx/docs/Status/Cxx26Papers.csv
+++ b/libcxx/docs/Status/Cxx26Papers.csv
@@ -61,7 +61,7 @@
 "`P1068R11 <https://wg21.link/P1068R11>`__","Vector API for random number generation","2024-03 (Tokyo)","","","`#105423 <https://github.com/llvm/llvm-project/issues/105423>`__",""
 "`P2944R3 <https://wg21.link/P2944R3>`__","Comparisons for ``reference_wrapper``","2024-03 (Tokyo)","|Complete|","22","`#105424 <https://github.com/llvm/llvm-project/issues/105424>`__","The changes to ``tuple``'s equality overload from P2165R4 are not yet implemented."
 "`P2642R6 <https://wg21.link/P2642R6>`__","Padded ``mdspan`` layouts","2024-03 (Tokyo)","","","`#105425 <https://github.com/llvm/llvm-project/issues/105425>`__",""
-"`P3029R1 <https://wg21.link/P3029R1>`__","Better ``mdspan``'s CTAD","2024-03 (Tokyo)","|Complete|","19","`#105426 <https://github.com/llvm/llvm-project/issues/105426>`__",""
+"`P3029R1 <https://wg21.link/P3029R1>`__","Better ``mdspan``'s CTAD","2024-03 (Tokyo)","|Complete|","19","`#105426 <https://github.com/llvm/llvm-project/issues/105426>`__","Implemented as a DR against C++20 since LLVM 23. (MSVC STL and libstdc++ will do the same.)"
 "","","","","","",""
 "`P2747R2 <https://wg21.link/P2747R2>`__","``constexpr`` placement new","2024-06 (St. Louis)","|Complete|","20","`#105427 <https://github.com/llvm/llvm-project/issues/105427>`__",""
 "`P2997R1 <https://wg21.link/P2997R1>`__","Removing the common reference requirement from the indirectly invocable concepts","2024-06 (St. Louis)","|Complete|","19","`#105428 <https://github.com/llvm/llvm-project/issues/105428>`__","Implemented as a DR against C++20. (MSVC STL and libstdc++ will do the same.)"
diff --git a/libcxx/include/__mdspan/extents.h b/libcxx/include/__mdspan/extents.h
index bc4490aa5b470..99366b9ca9cc4 100644
--- a/libcxx/include/__mdspan/extents.h
+++ b/libcxx/include/__mdspan/extents.h
@@ -466,15 +466,9 @@ using dims = dextents<_IndexType, _Rank>;
 #  endif
 
 // Deduction guide for extents
-#  if _LIBCPP_STD_VER >= 26
 template <class... _IndexTypes>
   requires(is_convertible_v<_IndexTypes, size_t> && ...)
 explicit extents(_IndexTypes...) -> extents<size_t, __maybe_static_ext<_IndexTypes>...>;
-#  else
-template <class... _IndexTypes>
-  requires(is_convertible_v<_IndexTypes, size_t> && ...)
-explicit extents(_IndexTypes...) -> extents<size_t, size_t(((void)sizeof(_IndexTypes), dynamic_extent))...>;
-#  endif
 
 namespace __mdspan_detail {
 
diff --git a/libcxx/include/__mdspan/mdspan.h b/libcxx/include/__mdspan/mdspan.h
index bcfa302231cd4..3b78f792770f0 100644
--- a/libcxx/include/__mdspan/mdspan.h
+++ b/libcxx/include/__mdspan/mdspan.h
@@ -312,17 +312,10 @@ class mdspan {
 #  endif
 };
 
-#  if _LIBCPP_STD_VER >= 26
 template <class _ElementType, class... _OtherIndexTypes>
   requires((is_convertible_v<_OtherIndexTypes, size_t> && ...) && (sizeof...(_OtherIndexTypes) > 0))
 explicit mdspan(_ElementType*, _OtherIndexTypes...)
     -> mdspan<_ElementType, extents<size_t, __maybe_static_ext<_OtherIndexTypes>...>>;
-#  else
-template <class _ElementType, class... _OtherIndexTypes>
-  requires((is_convertible_v<_OtherIndexTypes, size_t> && ...) && (sizeof...(_OtherIndexTypes) > 0))
-explicit mdspan(_ElementType*, _OtherIndexTypes...)
-    -> mdspan<_ElementType, dextents<size_t, sizeof...(_OtherIndexTypes)>>;
-#  endif
 
 template <class _Pointer>
   requires(is_pointer_v<remove_reference_t<_Pointer>>)
diff --git a/libcxx/include/mdspan b/libcxx/include/mdspan
index 2f0e1f9b87751..6313c03f041af 100644
--- a/libcxx/include/mdspan
+++ b/libcxx/include/mdspan
@@ -411,14 +411,10 @@ namespace std {
     mdspan(Pointer&&)
       -> mdspan<remove_pointer_t<remove_reference_t<Pointer>>, extents<size_t>>;
 
-  template<class ElementType, class... Integrals>
-    requires ((is_convertible_v<Integrals, size_t> && ...) && sizeof...(Integrals) > 0)
-    explicit mdspan(ElementType*, Integrals...)
-      -> mdspan<ElementType, dextents<size_t, sizeof...(Integrals)>>;            // until C++26
   template<class ElementType, class... Integrals>
     requires((is_convertible_v<Integrals, size_t> && ...) && sizeof...(Integrals) > 0)
     explicit mdspan(ElementType*, Integrals...)
-      -> mdspan<ElementType, extents<size_t, maybe-static-ext<Integrals>...>>;  // since C++26
+      -> mdspan<ElementType, extents<size_t, maybe-static-ext<Integrals>...>>;
 
   template<class ElementType, class OtherIndexType, size_t N>
     mdspan(ElementType*, span<OtherIndexType, N>)
diff --git a/libcxx/include/span b/libcxx/include/span
index e39b811f9b1ea..1c52983f808ca 100644
--- a/libcxx/include/span
+++ b/libcxx/include/span
@@ -19,16 +19,16 @@ namespace std {
 inline constexpr size_t dynamic_extent = numeric_limits<size_t>::max();
 
 template<class T>
-  concept integral-constant-like =                           // exposition only, since C++26
-    is_integral_v<decltype(T::value)> &&
-    !is_same_v<bool, remove_const_t<decltype(T::value)>> &&
+  concept integral-constant-like =                           // exposition only
+    is_integral_v<remove_cvref_t<decltype(T::value)>> &&
+    !is_same_v<bool, remove_cvref_t<decltype(T::value)>> &&
     convertible_to<T, decltype(T::value)> &&
     equality_comparable_with<T, decltype(T::value)> &&
     bool_constant<T() == T::value>::value &&
     bool_constant<static_cast<decltype(T::value)>(T()) == T::value>::value;
 
 template<class T>
-  constexpr size_t maybe-static-ext = dynamic_extent;        // exposition only, since C++26
+  constexpr size_t maybe-static-ext = dynamic_extent;        // exposition only
 template<integral-constant-like T>
   constexpr size_t maybe-static-ext<T> = {T::value};
 
@@ -122,8 +122,6 @@ private:
     size_type size_;  // exposition only
 };
 
-template<class It, class EndOrSize>
-    span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<_It>>>;                             // until C++26
 template<class It, class EndOrSize>
     span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<It>>, maybe-static-ext<EndOrSize>>; // since C++26
 
@@ -597,7 +595,6 @@ template <class _Tp, size_t _Extent>
   return __s.__as_writable_bytes();
 }
 
-#    if _LIBCPP_STD_VER >= 26
 template <class _Tp>
 concept __integral_constant_like =
     is_integral_v<remove_cvref_t<decltype(_Tp::value)>> && !is_same_v<bool, remove_cvref_t<decltype(_Tp::value)>> &&
@@ -613,10 +610,6 @@ inline constexpr size_t __maybe_static_ext<_Tp> = {_Tp::value};
 
 template <contiguous_iterator _It, class _EndOrSize>
 span(_It, _EndOrSize) -> span<remove_reference_t<iter_reference_t<_It>>, __maybe_static_ext<_EndOrSize>>;
-#    else
-template <contiguous_iterator _It, class _EndOrSize>
-span(_It, _EndOrSize) -> span<remove_reference_t<iter_reference_t<_It>>>;
-#    endif
 
 template <class _Tp, size_t _Sz>
 span(_Tp (&)[_Sz]) -> span<_Tp, _Sz>;
diff --git a/libcxx/test/std/containers/views/mdspan/extents/ctad.pass.cpp b/libcxx/test/std/containers/views/mdspan/extents/ctad.pass.cpp
index b72c3e050b1f9..e34113f91ad06 100644
--- a/libcxx/test/std/containers/views/mdspan/extents/ctad.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/extents/ctad.pass.cpp
@@ -13,8 +13,7 @@
 // explicit extents(Integrals...) -> see below;
 //   Constraints: (is_convertible_v<Integrals, size_t> && ...) is true.
 //
-// Remarks: The deduced type is dextents<size_t, sizeof...(Integrals)>.           // until C++26
-// Remarks: The deduced type is extents<size_t, maybe-static-ext<Integrals>...>.  // since C++26
+// Remarks: The deduced type is extents<size_t, maybe-static-ext<Integrals>...>.
 
 #include <cassert>
 #include <cstddef>
@@ -31,6 +30,9 @@ struct NoDefaultCtorIndex {
   constexpr operator size_t() const noexcept { return value; }
 };
 
+template <std::size_t N>
+using size_ref_constant = std::integral_constant<const std::size_t&, std::integral_constant<std::size_t, N>::value>;
+
 template <class E, class Expected>
 constexpr void test(E e, Expected expected) {
   ASSERT_SAME_TYPE(E, Expected);
@@ -47,13 +49,16 @@ constexpr bool test() {
        std::extents<std::size_t, D, D, D, D, D, D, D, D, D>(1, 2u, 3, 4, 5, 6, 7, 8, 9));
   test(std::extents(NoDefaultCtorIndex{1}, NoDefaultCtorIndex{2}), std::extents<std::size_t, D, D>(1, 2));
 
-#if _LIBCPP_STD_VER >= 26
   // P3029R1: deduction from `integral_constant`
   test(std::extents(std::integral_constant<size_t, 5>{}), std::extents<std::size_t, 5>());
   test(std::extents(std::integral_constant<size_t, 5>{}, 6), std::extents<std::size_t, 5, std::dynamic_extent>(6));
   test(std::extents(std::integral_constant<size_t, 5>{}, 6, std::integral_constant<size_t, 7>{}),
        std::extents<std::size_t, 5, std::dynamic_extent, 7>(6));
-#endif
+  // support for `T::value` of a reference type from P2781R9 `std::constant_wrapper`
+  test(std::extents(size_ref_constant<5>{}), std::extents<std::size_t, 5>());
+  test(std::extents(size_ref_constant<5>{}, 6), std::extents<std::size_t, 5, std::dynamic_extent>(6));
+  test(std::extents(std::integral_constant<size_t, 5>{}, 6, size_ref_constant<7>{}),
+       std::extents<std::size_t, 5, std::dynamic_extent, 7>(6));
 
   return true;
 }
diff --git a/libcxx/test/std/containers/views/mdspan/mdspan/deduction.pass.cpp b/libcxx/test/std/containers/views/mdspan/mdspan/deduction.pass.cpp
index 762841016e29e..6c12ccb7fd86a 100644
--- a/libcxx/test/std/containers/views/mdspan/mdspan/deduction.pass.cpp
+++ b/libcxx/test/std/containers/views/mdspan/mdspan/deduction.pass.cpp
@@ -22,11 +22,7 @@
 //  template<class ElementType, class... Integrals>
 //    requires((is_convertible_v<Integrals, size_t> && ...) && sizeof...(Integrals) > 0)
 //    explicit mdspan(ElementType*, Integrals...)
-//      -> mdspan<ElementType, dextents<size_t, sizeof...(Integrals)>>;            // until C++26
-//  template<class ElementType, class... Integrals>
-//    requires((is_convertible_v<Integrals, size_t> && ...) && sizeof...(Integrals) > 0)
-//    explicit mdspan(ElementType*, Integrals...)
-//      -> mdspan<ElementType, extents<size_t, maybe-static-ext<Integrals>...>>;  // since C++26
+//      -> mdspan<ElementType, extents<size_t, maybe-static-ext<Integrals>...>>;
 //
 //  template<class ElementType, class OtherIndexType, size_t N>
 //    mdspan(ElementType*, span<OtherIndexType, N>)
@@ -53,6 +49,7 @@
 
 #include <mdspan>
 #include <cassert>
+#include <cstddef>
 #include <concepts>
 #include <span> // dynamic_extent
 #include <type_traits>
@@ -98,6 +95,9 @@ struct SizeTIntType {
   constexpr operator size_t() const noexcept { return size_t(val); }
 };
 
+template <std::size_t N>
+using size_ref_constant = std::integral_constant<const std::size_t&, std::integral_constant<std::size_t, N>::value>;
+
 template <class H, class A>
   requires(sizeof(decltype(std::mdspan(std::declval<H>(), 10))) > 0)
 constexpr bool test_no_layout_deduction_guides(const H& handle, const A&) {
@@ -107,7 +107,6 @@ constexpr bool test_no_layout_deduction_guides(const H& handle, const A&) {
   // deduction from pointer and integral like
   ASSERT_SAME_TYPE(decltype(std::mdspan(handle, 5, SizeTIntType(6))), std::mdspan<T, std::dextents<size_t, 2>>);
 
-#if _LIBCPP_STD_VER >= 26
   // P3029R1: deduction from `integral_constant`
   ASSERT_SAME_TYPE(
       decltype(std::mdspan(handle, std::integral_constant<size_t, 5>{})), std::mdspan<T, std::extents<size_t, 5>>);
@@ -117,7 +116,13 @@ constexpr bool test_no_layout_deduction_guides(const H& handle, const A&) {
       decltype(std::mdspan(
           handle, std::integral_constant<size_t, 5>{}, std::dynamic_extent, std::integral_constant<size_t, 7>{})),
       std::mdspan<T, std::extents<size_t, 5, std::dynamic_extent, 7>>);
-#endif
+  // support for `T::value` of a reference type from P2781R9 `std::constant_wrapper`
+  ASSERT_SAME_TYPE(decltype(std::mdspan(handle, size_ref_constant<5>{})), std::mdspan<T, std::extents<std::size_t, 5>>);
+  ASSERT_SAME_TYPE(decltype(std::mdspan(handle, size_ref_constant<5>{}, std::dynamic_extent)),
+                   std::mdspan<T, std::extents<std::size_t, 5, std::dynamic_extent>>);
+  ASSERT_SAME_TYPE(decltype(std::mdspan(
+                       handle, std::integral_constant<std::size_t, 5>{}, std::dynamic_extent, size_ref_constant<7>{})),
+                   std::mdspan<T, std::extents<std::size_t, 5, std::dynamic_extent, 7>>);
 
   std::array<char, 3> exts;
   // deduction from pointer and array
diff --git a/libcxx/test/std/containers/views/views.span/span.cons/deduct.pass.cpp b/libcxx/test/std/containers/views/views.span/span.cons/deduct.pass.cpp
index 9e41afad2258e..35d9b36f824aa 100644
--- a/libcxx/test/std/containers/views/views.span/span.cons/deduct.pass.cpp
+++ b/libcxx/test/std/containers/views/views.span/span.cons/deduct.pass.cpp
@@ -11,10 +11,7 @@
 // <span>
 
 // template<class It, class EndOrSize>
-//   span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<It>>>; // until C++26
-// template<class It, class EndOrSize>
-//   span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<It>>,
-//                               maybe-static-ext<EndOrSize>>; // since C++26
+//   span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<It>>, maybe-static-ext<EndOrSize>>;
 // template<class T, size_t N>
 //   span(T (&)[N]) -> span<T, N>;
 // template<class T, size_t N>
@@ -27,6 +24,7 @@
 #include <span>
 #include <array>
 #include <cassert>
+#include <cstddef>
 #include <iterator>
 #include <memory>
 #include <string>
@@ -50,7 +48,6 @@ void test_iterator_sentinel() {
     assert(s.data() == std::data(arr));
   }
 
-#if TEST_STD_VER >= 26
   // P3029R1: deduction from `integral_constant`
   {
     std::span s{std::begin(arr), std::integral_constant<size_t, 3>{}};
@@ -58,15 +55,33 @@ void test_iterator_sentinel() {
     assert(s.size() == 3);
     assert(s.data() == std::data(arr));
   }
-
+  // support for `T::value` of a reference type from P2781R9 `std::constant_wrapper`
+  {
+    using size_3_ref_type = std::integral_constant<const std::size_t&, std::integral_constant<std::size_t, 3>::value>;
+    std::span s{std::begin(arr), size_3_ref_type{}};
+    ASSERT_SAME_TYPE(decltype(s), std::span<int, 3>);
+    assert(s.size() == 3);
+    assert(s.data() == std::data(arr));
+  }
+#if TEST_STD_VER >= 26
   {
     std::span s{std::begin(arr), std::cw<3>};
     ASSERT_SAME_TYPE(decltype(s), std::span<int, 3>);
     assert(s.size() == 3);
     assert(s.data() == std::data(arr));
   }
+#endif
   {
     // LWG4351 integral-constant-like needs more remove_cvref_t
+    using true_ref_type = std::integral_constant<const bool&, std::true_type::value>;
+    LIBCPP_STATIC_ASSERT(!std::__integral_constant_like<true_ref_type>);
+    std::span s(std::begin(arr), true_ref_type{});
+    ASSERT_SAME_TYPE(decltype(s), std::span<int, std::dynamic_extent>);
+    assert(s.size() == 1);
+    assert(s.data() == std::data(arr));
+  }
+#if TEST_STD_VER >= 26
+  {
     LIBCPP_STATIC_ASSERT(!std::__integral_constant_like<decltype(std::cw<true>)>);
     std::span s(std::begin(arr), std::cw<true>);
     ASSERT_SAME_TYPE(decltype(s), std::span<int, std::dynamic_extent>);

>From 5108cca329c7c1374047312f970ffdf1aa72431b Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Wed, 8 Jul 2026 13:41:37 +0800
Subject: [PATCH 2/2] Remove one `// since C++26`; avoid mentioning other
 implementations

---
 libcxx/docs/Status/Cxx26Papers.csv | 2 +-
 libcxx/include/span                | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/libcxx/docs/Status/Cxx26Papers.csv b/libcxx/docs/Status/Cxx26Papers.csv
index e1fcaf907c95b..2522dd98e4b9c 100644
--- a/libcxx/docs/Status/Cxx26Papers.csv
+++ b/libcxx/docs/Status/Cxx26Papers.csv
@@ -61,7 +61,7 @@
 "`P1068R11 <https://wg21.link/P1068R11>`__","Vector API for random number generation","2024-03 (Tokyo)","","","`#105423 <https://github.com/llvm/llvm-project/issues/105423>`__",""
 "`P2944R3 <https://wg21.link/P2944R3>`__","Comparisons for ``reference_wrapper``","2024-03 (Tokyo)","|Complete|","22","`#105424 <https://github.com/llvm/llvm-project/issues/105424>`__","The changes to ``tuple``'s equality overload from P2165R4 are not yet implemented."
 "`P2642R6 <https://wg21.link/P2642R6>`__","Padded ``mdspan`` layouts","2024-03 (Tokyo)","","","`#105425 <https://github.com/llvm/llvm-project/issues/105425>`__",""
-"`P3029R1 <https://wg21.link/P3029R1>`__","Better ``mdspan``'s CTAD","2024-03 (Tokyo)","|Complete|","19","`#105426 <https://github.com/llvm/llvm-project/issues/105426>`__","Implemented as a DR against C++20 since LLVM 23. (MSVC STL and libstdc++ will do the same.)"
+"`P3029R1 <https://wg21.link/P3029R1>`__","Better ``mdspan``'s CTAD","2024-03 (Tokyo)","|Complete|","19","`#105426 <https://github.com/llvm/llvm-project/issues/105426>`__","Implemented as a DR against C++20 since LLVM 23."
 "","","","","","",""
 "`P2747R2 <https://wg21.link/P2747R2>`__","``constexpr`` placement new","2024-06 (St. Louis)","|Complete|","20","`#105427 <https://github.com/llvm/llvm-project/issues/105427>`__",""
 "`P2997R1 <https://wg21.link/P2997R1>`__","Removing the common reference requirement from the indirectly invocable concepts","2024-06 (St. Louis)","|Complete|","19","`#105428 <https://github.com/llvm/llvm-project/issues/105428>`__","Implemented as a DR against C++20. (MSVC STL and libstdc++ will do the same.)"
diff --git a/libcxx/include/span b/libcxx/include/span
index 1c52983f808ca..b05a953829b5a 100644
--- a/libcxx/include/span
+++ b/libcxx/include/span
@@ -123,7 +123,7 @@ private:
 };
 
 template<class It, class EndOrSize>
-    span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<It>>, maybe-static-ext<EndOrSize>>; // since C++26
+    span(It, EndOrSize) -> span<remove_reference_t<iter_reference_t<It>>, maybe-static-ext<EndOrSize>>;
 
 template<class T, size_t N>
     span(T (&)[N]) -> span<T, N>;



More information about the libcxx-commits mailing list