[libcxx-commits] [libcxx] [libc++] P3391R2: `constexpr` integral overloads of `to_(w)string` (PR #205025)

A. Jiang via libcxx-commits libcxx-commits at lists.llvm.org
Sun Jun 21 20:23:18 PDT 2026


https://github.com/frederick-vs-ja created https://github.com/llvm/llvm-project/pull/205025

Implements a small part of P3391R2. Also implements related LWG4531.

Fixes #204454.

>From 5b0c184b98c5cdce7f20e76b8208a3c219e29b94 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Mon, 22 Jun 2026 11:17:25 +0800
Subject: [PATCH] [libc++] P3391R2: `constexpr` integral overloads of
 `to_(w)string`

Also implements related LWG4531.
---
 libcxx/docs/FeatureTestMacroTable.rst         |  2 +
 libcxx/docs/Status/Cxx26Papers.csv            |  2 +-
 libcxx/docs/Status/Cxx29Issues.csv            |  2 +-
 libcxx/include/string                         | 55 +++++++++++
 libcxx/include/version                        |  5 +-
 libcxx/src/string.cpp                         | 50 +++++-----
 .../string.version.compile.pass.cpp           |  4 +-
 .../version.version.compile.pass.cpp          |  4 +-
 .../string.conversions/to_string.pass.cpp     | 15 ++-
 .../string.conversions/to_wstring.pass.cpp    | 15 ++-
 libcxx/test/support/parse_integer.h           | 99 +++++++++++++++----
 .../generate_feature_test_macro_components.py |  2 +-
 12 files changed, 197 insertions(+), 58 deletions(-)

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 8e26bdcd860c1..3baa0acb5c06a 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -450,6 +450,8 @@ Status
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_constexpr_queue``                              ``202502L``
     ---------------------------------------------------------- -----------------
+    ``__cpp_lib_constexpr_string``                             ``202511L``
+    ---------------------------------------------------------- -----------------
     ``__cpp_lib_constrained_equality``                         ``202411L``
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_copyable_function``                            *unimplemented*
diff --git a/libcxx/docs/Status/Cxx26Papers.csv b/libcxx/docs/Status/Cxx26Papers.csv
index a951ac4c6c833..8dce1ed74811a 100644
--- a/libcxx/docs/Status/Cxx26Papers.csv
+++ b/libcxx/docs/Status/Cxx26Papers.csv
@@ -175,7 +175,7 @@
 "`P3887R1 <https://wg21.link/P3887R1>`__","Make ``when_all`` a Ronseal Algorithm","2025-11 (Kona)","","","`#171289 <https://github.com/llvm/llvm-project/issues/171289>`__",""
 "`P3923R0 <https://wg21.link/P3923R0>`__","Additional NB comment resolutions for Kona 2025","2025-11 (Kona)","","","`#171290 <https://github.com/llvm/llvm-project/issues/171290>`__",""
 "`P3371R5 <https://wg21.link/P3371R5>`__","Fix C++26 by making the rank-1, rank-2, rank-k, and rank-2k updates consistent with the BLAS","2025-11 (Kona)","","","`#171291 <https://github.com/llvm/llvm-project/issues/171291>`__",""
-"`P3391R2 <https://wg21.link/P3391R2>`__","``constexpr`` ``std::format``","2025-11 (Kona)","","","`#171292 <https://github.com/llvm/llvm-project/issues/171292>`__",""
+"`P3391R2 <https://wg21.link/P3391R2>`__","``constexpr`` ``std::format``","2025-11 (Kona)","|Partial|","","`#171292 <https://github.com/llvm/llvm-project/issues/171292>`__","``std::to_(w)string`` parts are implemented in LLVM 23."
 "`P3913R1 <https://wg21.link/P3913R1>`__","Optimize ``for std::optional`` in range adaptors","2025-11 (Kona)","","","`#171294 <https://github.com/llvm/llvm-project/issues/171294>`__",""
 "","","","","","",""
 "`P3726R2 <https://wg21.link/P3726R2>`__","Adjustments to Union Lifetime Rules","2026-03 (Croydon)","","","`#189560 <https://github.com/llvm/llvm-project/issues/189560>`__",""
diff --git a/libcxx/docs/Status/Cxx29Issues.csv b/libcxx/docs/Status/Cxx29Issues.csv
index d9e245a9a11c0..072ab8effa121 100644
--- a/libcxx/docs/Status/Cxx29Issues.csv
+++ b/libcxx/docs/Status/Cxx29Issues.csv
@@ -13,7 +13,7 @@
 "`LWG4487 <https://wg21.link/LWG4487>`__","Is member ``is_steady`` of a *Cpp17Clock* type required to be usable in constant expressions?","2026-06 (Brno)","","","`#204451 <https://github.com/llvm/llvm-project/issues/204451>`__",""
 "`LWG4490 <https://wg21.link/LWG4490>`__","Allow calling ``std::ranges::size`` in ranges algorithms","2026-06 (Brno)","","","`#204452 <https://github.com/llvm/llvm-project/issues/204452>`__",""
 "`LWG4521 <https://wg21.link/LWG4521>`__","Improve [atomics.order] p10 to have a consistent way with [intro.races]","2026-06 (Brno)","","","`#204453 <https://github.com/llvm/llvm-project/issues/204453>`__",""
-"`LWG4531 <https://wg21.link/LWG4531>`__","Should there be a feature-test macro update for ``constexpr`` ``std::to_(w)string``?","2026-06 (Brno)","","","`#204454 <https://github.com/llvm/llvm-project/issues/204454>`__",""
+"`LWG4531 <https://wg21.link/LWG4531>`__","Should there be a feature-test macro update for ``constexpr`` ``std::to_(w)string``?","2026-06 (Brno)","|Complete|","23","`#204454 <https://github.com/llvm/llvm-project/issues/204454>`__",""
 "`LWG4567 <https://wg21.link/LWG4567>`__","Feature test macro value for ``apply_result``, ``is_applicable``","2026-06 (Brno)","","","`#204455 <https://github.com/llvm/llvm-project/issues/204455>`__",""
 "`LWG4568 <https://wg21.link/LWG4568>`__","``std::execution::spawn_future`` is mishandling dependent senders","2026-06 (Brno)","","","`#204456 <https://github.com/llvm/llvm-project/issues/204456>`__",""
 "`LWG4579 <https://wg21.link/LWG4579>`__","``make-state<Rcvr>::state-type::complete`` uses ``Env`` which is not in scope","2026-06 (Brno)","","","`#204457 <https://github.com/llvm/llvm-project/issues/204457>`__",""
diff --git a/libcxx/include/string b/libcxx/include/string
index e5bc854cd34f3..531ee2458b944 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -663,6 +663,12 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
 #    include <cwchar>
 #  endif
 
+#  if _LIBCPP_STD_VER >= 26
+#    include <__charconv/to_chars_integral.h>
+#    include <__charconv/to_chars_result.h>
+#    include <__system_error/errc.h>
+#  endif
+
 // standard-mandated includes
 
 // [iterator.range]
@@ -3774,12 +3780,41 @@ stoull(const string& __str, size_t* __idx = nullptr, int __base = 10);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI double stod(const string& __str, size_t* __idx = nullptr);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI long double stold(const string& __str, size_t* __idx = nullptr);
 
+#  if _LIBCPP_STD_VER >= 26
+
+template <class _String, class _Integer>
+constexpr _String __integer_to_string(_Integer __val) {
+  // numeric_limits::digits10 returns value less on 1 than desired for unsigned numbers.
+  // For example, for 1-byte unsigned value digits10 is 2 (999 can not be represented), so we need +1 here.
+  constexpr size_t __buf_size = numeric_limits<_Integer>::digits10 + 2; // +1 for minus, +1 for digits10
+  char __buf[__buf_size];
+  const auto [__ptr, __ec] = std::to_chars(__buf, __buf + __buf_size, __val);
+  _LIBCPP_ASSERT_INTERNAL(__ec == errc(), "__buf_size must be large enough to accomodate the value");
+  return _String(__buf, __ptr);
+}
+
+inline namespace __cpp26 {
+
+[[nodiscard]] constexpr string to_string(int __val) { return std::__integer_to_string<string>(__val); }
+[[nodiscard]] constexpr string to_string(unsigned __val) { return std::__integer_to_string<string>(__val); }
+[[nodiscard]] constexpr string to_string(long __val) { return std::__integer_to_string<string>(__val); }
+[[nodiscard]] constexpr string to_string(unsigned long __val) { return std::__integer_to_string<string>(__val); }
+[[nodiscard]] constexpr string to_string(long long __val) { return std::__integer_to_string<string>(__val); }
+[[nodiscard]] constexpr string to_string(unsigned long long __val) { return std::__integer_to_string<string>(__val); }
+
+} // namespace __cpp26
+
+#  else // _LIBCPP_STD_VER >= 26
+
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(int __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(unsigned __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(long __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(unsigned long __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(long long __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(unsigned long long __val);
+
+#  endif // _LIBCPP_STD_VER >= 26
+
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(float __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(double __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(long double __val);
@@ -3798,12 +3833,32 @@ stoull(const wstring& __str, size_t* __idx = nullptr, int __base = 10);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI double stod(const wstring& __str, size_t* __idx = nullptr);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI long double stold(const wstring& __str, size_t* __idx = nullptr);
 
+#    if _LIBCPP_STD_VER >= 26
+
+inline namespace __cpp26 {
+
+[[nodiscard]] constexpr wstring to_wstring(int __val) { return std::__integer_to_string<wstring>(__val); }
+[[nodiscard]] constexpr wstring to_wstring(unsigned __val) { return std::__integer_to_string<wstring>(__val); }
+[[nodiscard]] constexpr wstring to_wstring(long __val) { return std::__integer_to_string<wstring>(__val); }
+[[nodiscard]] constexpr wstring to_wstring(unsigned long __val) { return std::__integer_to_string<wstring>(__val); }
+[[nodiscard]] constexpr wstring to_wstring(long long __val) { return std::__integer_to_string<wstring>(__val); }
+[[nodiscard]] constexpr wstring to_wstring(unsigned long long __val) {
+  return std::__integer_to_string<wstring>(__val);
+}
+
+} // namespace __cpp26
+
+#    else // _LIBCPP_STD_VER >= 26
+
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(int __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(unsigned __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(long __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(unsigned long __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(long long __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(unsigned long long __val);
+
+#    endif // _LIBCPP_STD_VER >= 26
+
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(float __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(double __val);
 [[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(long double __val);
diff --git a/libcxx/include/version b/libcxx/include/version
index 7f2dc9e4b72ab..ed278dff0f59f 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -84,7 +84,8 @@ __cpp_lib_constexpr_memory                              202202L <memory>
 __cpp_lib_constexpr_new                                 202406L <new>
 __cpp_lib_constexpr_numeric                             201911L <numeric>
 __cpp_lib_constexpr_queue                               202502L <queue>
-__cpp_lib_constexpr_string                              201907L <string>
+__cpp_lib_constexpr_string                              202511L <string>
+                                                        201907L // C++20
 __cpp_lib_constexpr_string_view                         201811L <string_view>
 __cpp_lib_constexpr_tuple                               201811L <tuple>
 __cpp_lib_constexpr_typeinfo                            202106L <typeinfo>
@@ -575,6 +576,8 @@ __cpp_lib_void_t                                        201411L <type_traits>
 #   define __cpp_lib_constexpr_new                      202406L
 # endif
 # define __cpp_lib_constexpr_queue                      202502L
+# undef  __cpp_lib_constexpr_string
+# define __cpp_lib_constexpr_string                     202511L
 # define __cpp_lib_constrained_equality                 202411L
 // # define __cpp_lib_copyable_function                    202306L
 // # define __cpp_lib_debugging                            202311L
diff --git a/libcxx/src/string.cpp b/libcxx/src/string.cpp
index b55f9ff5c9a43..71a0689eecc17 100644
--- a/libcxx/src/string.cpp
+++ b/libcxx/src/string.cpp
@@ -328,34 +328,36 @@ inline wide_printf get_swprintf() {
 }
 #endif // _LIBCPP_HAS_WIDE_CHARACTERS
 
-template <typename S, typename V>
-S i_to_string(V v) {
-  //  numeric_limits::digits10 returns value less on 1 than desired for unsigned numbers.
-  //  For example, for 1-byte unsigned value digits10 is 2 (999 can not be represented),
-  //  so we need +1 here.
-  constexpr size_t bufsize = numeric_limits<V>::digits10 + 2; // +1 for minus, +1 for digits10
-  char buf[bufsize];
-  const auto res = to_chars(buf, buf + bufsize, v);
-  _LIBCPP_ASSERT_INTERNAL(res.ec == errc(), "bufsize must be large enough to accomodate the value");
-  return S(buf, res.ptr);
-}
-
 } // unnamed namespace
 
-string to_string(int val) { return i_to_string< string>(val); }
-string to_string(long val) { return i_to_string< string>(val); }
-string to_string(long long val) { return i_to_string< string>(val); }
-string to_string(unsigned val) { return i_to_string< string>(val); }
-string to_string(unsigned long val) { return i_to_string< string>(val); }
-string to_string(unsigned long long val) { return i_to_string< string>(val); }
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(int);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(unsigned);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(long);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(unsigned long);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(long long);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI string to_string(unsigned long long);
+
+string to_string(int val) { return __cpp26::to_string(val); }
+string to_string(long val) { return __cpp26::to_string(val); }
+string to_string(long long val) { return __cpp26::to_string(val); }
+string to_string(unsigned val) { return __cpp26::to_string(val); }
+string to_string(unsigned long val) { return __cpp26::to_string(val); }
+string to_string(unsigned long long val) { return __cpp26::to_string(val); }
 
 #if _LIBCPP_HAS_WIDE_CHARACTERS
-wstring to_wstring(int val) { return i_to_string<wstring>(val); }
-wstring to_wstring(long val) { return i_to_string<wstring>(val); }
-wstring to_wstring(long long val) { return i_to_string<wstring>(val); }
-wstring to_wstring(unsigned val) { return i_to_string<wstring>(val); }
-wstring to_wstring(unsigned long val) { return i_to_string<wstring>(val); }
-wstring to_wstring(unsigned long long val) { return i_to_string<wstring>(val); }
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(int);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(unsigned);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(long);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(unsigned long);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(long long);
+[[__nodiscard__]] _LIBCPP_EXPORTED_FROM_ABI wstring to_wstring(unsigned long long);
+
+wstring to_wstring(int val) { return __cpp26::to_wstring(val); }
+wstring to_wstring(long val) { return __cpp26::to_wstring(val); }
+wstring to_wstring(long long val) { return __cpp26::to_wstring(val); }
+wstring to_wstring(unsigned val) { return __cpp26::to_wstring(val); }
+wstring to_wstring(unsigned long val) { return __cpp26::to_wstring(val); }
+wstring to_wstring(unsigned long long val) { return __cpp26::to_wstring(val); }
 #endif
 
 string to_string(float val) { return as_string(snprintf, initial_string< string>()(), "%f", val); }
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/string.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/string.version.compile.pass.cpp
index 147854eead2cc..324dc4dbe3200 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/string.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/string.version.compile.pass.cpp
@@ -417,8 +417,8 @@
 #  ifndef __cpp_lib_constexpr_string
 #    error "__cpp_lib_constexpr_string should be defined in c++26"
 #  endif
-#  if __cpp_lib_constexpr_string != 201907L
-#    error "__cpp_lib_constexpr_string should have the value 201907L in c++26"
+#  if __cpp_lib_constexpr_string != 202511L
+#    error "__cpp_lib_constexpr_string should have the value 202511L in c++26"
 #  endif
 
 #  ifndef __cpp_lib_containers_ranges
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index d9c78b73f7e23..aed1162977efc 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -6747,8 +6747,8 @@
 #  ifndef __cpp_lib_constexpr_string
 #    error "__cpp_lib_constexpr_string should be defined in c++26"
 #  endif
-#  if __cpp_lib_constexpr_string != 201907L
-#    error "__cpp_lib_constexpr_string should have the value 201907L in c++26"
+#  if __cpp_lib_constexpr_string != 202511L
+#    error "__cpp_lib_constexpr_string should have the value 202511L in c++26"
 #  endif
 
 #  ifndef __cpp_lib_constexpr_string_view
diff --git a/libcxx/test/std/strings/string.conversions/to_string.pass.cpp b/libcxx/test/std/strings/string.conversions/to_string.pass.cpp
index 4731a072e8910..a36d21592b08d 100644
--- a/libcxx/test/std/strings/string.conversions/to_string.pass.cpp
+++ b/libcxx/test/std/strings/string.conversions/to_string.pass.cpp
@@ -26,7 +26,7 @@
 #include "test_macros.h"
 
 template <class T>
-void test_signed() {
+TEST_CONSTEXPR_CXX26 void test_signed() {
   {
     std::string s = std::to_string(T(0));
     assert(s.size() == 1);
@@ -59,7 +59,7 @@ void test_signed() {
 }
 
 template <class T>
-void test_unsigned() {
+TEST_CONSTEXPR_CXX26 void test_unsigned() {
   {
     std::string s = std::to_string(T(0));
     assert(s.size() == 1);
@@ -102,16 +102,25 @@ void test_float() {
   }
 }
 
-int main(int, char**) {
+TEST_CONSTEXPR_CXX26 bool test_integers() {
   test_signed<int>();
   test_signed<long>();
   test_signed<long long>();
   test_unsigned<unsigned>();
   test_unsigned<unsigned long>();
   test_unsigned<unsigned long long>();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_integers();
   test_float<float>();
   test_float<double>();
   test_float<long double>();
+#if TEST_STD_VER >= 26
+  static_assert(test_integers());
+#endif
 
   return 0;
 }
diff --git a/libcxx/test/std/strings/string.conversions/to_wstring.pass.cpp b/libcxx/test/std/strings/string.conversions/to_wstring.pass.cpp
index fff5ede848b57..22d06bffb84b8 100644
--- a/libcxx/test/std/strings/string.conversions/to_wstring.pass.cpp
+++ b/libcxx/test/std/strings/string.conversions/to_wstring.pass.cpp
@@ -28,7 +28,7 @@
 #include "test_macros.h"
 
 template <class T>
-void test_signed() {
+TEST_CONSTEXPR_CXX26 void test_signed() {
   {
     std::wstring s = std::to_wstring(T(0));
     assert(s.size() == 1);
@@ -61,7 +61,7 @@ void test_signed() {
 }
 
 template <class T>
-void test_unsigned() {
+TEST_CONSTEXPR_CXX26 void test_unsigned() {
   {
     std::wstring s = std::to_wstring(T(0));
     assert(s.size() == 1);
@@ -104,16 +104,25 @@ void test_float() {
   }
 }
 
-int main(int, char**) {
+TEST_CONSTEXPR_CXX26 bool test_integers() {
   test_signed<int>();
   test_signed<long>();
   test_signed<long long>();
   test_unsigned<unsigned>();
   test_unsigned<unsigned long>();
   test_unsigned<unsigned long long>();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_integers();
   test_float<float>();
   test_float<double>();
   test_float<long double>();
+#if TEST_STD_VER >= 26
+  static_assert(test_integers());
+#endif
 
   return 0;
 }
diff --git a/libcxx/test/support/parse_integer.h b/libcxx/test/support/parse_integer.h
index 36f8f7b79881b..90f5541d222ef 100644
--- a/libcxx/test/support/parse_integer.h
+++ b/libcxx/test/support/parse_integer.h
@@ -9,7 +9,12 @@
 #ifndef LIBCPP_TEST_SUPPORT_PARSE_INTEGER_H
 #define LIBCPP_TEST_SUPPORT_PARSE_INTEGER_H
 
+#include <charconv>
+#include <cstdlib>
 #include <string>
+#include <system_error>
+
+#include "test_macros.h"
 
 namespace detail {
 template <class T>
@@ -17,56 +22,110 @@ struct parse_integer_impl;
 
 template <>
 struct parse_integer_impl<int> {
-    template <class CharT>
-    int operator()(std::basic_string<CharT> const& str) const {
-        return std::stoi(str);
+  template <class CharT>
+  TEST_CONSTEXPR_CXX23 int operator()(std::basic_string<CharT> const& str) const {
+#if TEST_STD_VER >= 23
+    if consteval {
+      int n;
+      auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), n);
+      if (ec != std::errc{})
+        std::abort();
+      return n;
     }
+#endif
+    return std::stoi(str);
+  }
 };
 
 template <>
 struct parse_integer_impl<long> {
-    template <class CharT>
-    long operator()(std::basic_string<CharT> const& str) const {
-        return std::stol(str);
+  template <class CharT>
+  TEST_CONSTEXPR_CXX23 long operator()(std::basic_string<CharT> const& str) const {
+#if TEST_STD_VER >= 23
+    if consteval {
+      long n;
+      auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), n);
+      if (ec != std::errc{})
+        std::abort();
+      return n;
     }
+#endif
+    return std::stol(str);
+  }
 };
 
 template <>
 struct parse_integer_impl<long long> {
-    template <class CharT>
-    long long operator()(std::basic_string<CharT> const& str) const {
-        return std::stoll(str);
+  template <class CharT>
+  TEST_CONSTEXPR_CXX23 long long operator()(std::basic_string<CharT> const& str) const {
+#if TEST_STD_VER >= 23
+    if consteval {
+      long long n;
+      auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), n);
+      if (ec != std::errc{})
+        std::abort();
+      return n;
     }
+#endif
+    return std::stoll(str);
+  }
 };
 
 template <>
 struct parse_integer_impl<unsigned int> {
-    template <class CharT>
-    unsigned int operator()(std::basic_string<CharT> const& str) const {
-        return std::stoul(str);
+  template <class CharT>
+  TEST_CONSTEXPR_CXX23 unsigned int operator()(std::basic_string<CharT> const& str) const {
+#if TEST_STD_VER >= 23
+    if consteval {
+      unsigned int n;
+      auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), n);
+      if (ec != std::errc{})
+        std::abort();
+      return n;
     }
+#endif
+    return std::stoul(str);
+  }
 };
 
 template <>
 struct parse_integer_impl<unsigned long> {
-    template <class CharT>
-    unsigned long operator()(std::basic_string<CharT> const& str) const {
-        return std::stoul(str);
+  template <class CharT>
+  TEST_CONSTEXPR_CXX23 unsigned long operator()(std::basic_string<CharT> const& str) const {
+#if TEST_STD_VER >= 23
+    if consteval {
+      unsigned long n;
+      auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), n);
+      if (ec != std::errc{})
+        std::abort();
+      return n;
     }
+#endif
+    return std::stoul(str);
+  }
 };
 
 template <>
 struct parse_integer_impl<unsigned long long> {
-    template <class CharT>
-    unsigned long long operator()(std::basic_string<CharT> const& str) const {
-        return std::stoull(str);
+  template <class CharT>
+  TEST_CONSTEXPR_CXX23 unsigned long long operator()(std::basic_string<CharT> const& str) const {
+#if TEST_STD_VER >= 23
+    if consteval {
+      unsigned long long n;
+      auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), n);
+      if (ec != std::errc{})
+        std::abort();
+      return n;
     }
+#endif
+    return std::stoull(str);
+  }
 };
 } // namespace detail
 
 template <class T, class CharT>
-T parse_integer(std::basic_string<CharT> const& str) {
-    return detail::parse_integer_impl<T>()(str);
+TEST_CONSTEXPR_CXX23 T parse_integer(std::basic_string<CharT> const& str) {
+  return detail::parse_integer_impl<T>()(str);
 }
 
 #endif // LIBCPP_TEST_SUPPORT_PARSE_INTEGER_H
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index d764a1f677ba6..1f4961fda4d6f 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -434,7 +434,7 @@ def add_version_header(tc):
         },
         {
             "name": "__cpp_lib_constexpr_string",
-            "values": {"c++20": 201907},
+            "values": {"c++20": 201907, "c++26": 202511},
             "headers": ["string"],
         },
         {



More information about the libcxx-commits mailing list