[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
Tue Jul 7 19:23:21 PDT 2026
https://github.com/frederick-vs-ja created https://github.com/llvm/llvm-project/pull/208132
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.
Fixes #207727.
>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] [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>);
More information about the libcxx-commits
mailing list