libstdc++: mark integer std::to_(w)string constexpr

In C++26 paper P3391, "constexpr formatting", has been adopted,
part of which marks std::to_string & std::to_wstring for integers
as constexpr. The __cpp_lib_constexpr_string FTM value is updated
per resolution of LWG4531, "Should there be a feature-test macro
update for constexpr std::to_(w)string?".

Since pre-cxx11 copy-on-write string is not constexpr-enabled,
restricting this constexpr-ification to cxx11 ABI strings.

libstdc++-v3/ChangeLog:

	* include/bits/version.def (constexpr_string): Bump to 202511.
	* include/bits/version.h: Regenerate.
	* include/bits/basic_string.h (std::to_string, std::to_wstring)
	[__glibcxx_constexpr_string >= 202511L]: Mark as constexpr.
	* testsuite/21_strings/basic_string/numeric_conversions/char/to_string_constexpr.cc:
	New test.
	* testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring_constexpr.cc:
	New test.
	* testsuite/21_strings/basic_string/cons/char/constexpr.cc: Update
	__cpp_lib_constexpr_string check.
	* testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc: Likewise.
	* testsuite/21_strings/basic_string/version.cc: Add check for value of
	__cpp_lib_constexpr_string in C++26.

Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
Reviewed-by: Nathan Myers <ncm@cantrip.org>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
diff --git a/libstdc++-v3/include/bits/basic_string.h b/libstdc++-v3/include/bits/basic_string.h
index 23e6979..65d92eb 100644
--- a/libstdc++-v3/include/bits/basic_string.h
+++ b/libstdc++-v3/include/bits/basic_string.h
@@ -4607,11 +4607,17 @@
   { return std::stod(__str, __idx); }
 #endif
 
+#if __glibcxx_constexpr_string >= 202511L
+# define _GLIBCXX_TO_STRING_CONSTEXPR constexpr
+#else
+# define _GLIBCXX_TO_STRING_CONSTEXPR inline
+#endif
+
   // _GLIBCXX_RESOLVE_LIB_DEFECTS
   // DR 1261. Insufficient overloads for to_string / to_wstring
 
   _GLIBCXX_NODISCARD
-  inline string
+  _GLIBCXX_TO_STRING_CONSTEXPR string
   to_string(int __val)
 #if _GLIBCXX_USE_CXX11_ABI && (__CHAR_BIT__ * __SIZEOF_INT__) <= 32
   noexcept // any 32-bit value fits in the SSO buffer
@@ -4630,7 +4636,7 @@
   }
 
   _GLIBCXX_NODISCARD
-  inline string
+  _GLIBCXX_TO_STRING_CONSTEXPR string
   to_string(unsigned __val)
 #if _GLIBCXX_USE_CXX11_ABI && (__CHAR_BIT__ * __SIZEOF_INT__) <= 32
   noexcept // any 32-bit value fits in the SSO buffer
@@ -4646,7 +4652,7 @@
   }
 
   _GLIBCXX_NODISCARD
-  inline string
+  _GLIBCXX_TO_STRING_CONSTEXPR string
   to_string(long __val)
 #if _GLIBCXX_USE_CXX11_ABI && (__CHAR_BIT__ * __SIZEOF_LONG__) <= 32
   noexcept // any 32-bit value fits in the SSO buffer
@@ -4665,7 +4671,7 @@
   }
 
   _GLIBCXX_NODISCARD
-  inline string
+  _GLIBCXX_TO_STRING_CONSTEXPR string
   to_string(unsigned long __val)
 #if _GLIBCXX_USE_CXX11_ABI && (__CHAR_BIT__ * __SIZEOF_LONG__) <= 32
   noexcept // any 32-bit value fits in the SSO buffer
@@ -4681,7 +4687,7 @@
   }
 
   _GLIBCXX_NODISCARD
-  inline string
+  _GLIBCXX_TO_STRING_CONSTEXPR string
   to_string(long long __val)
   {
     const bool __neg = __val < 0;
@@ -4698,7 +4704,7 @@
   }
 
   _GLIBCXX_NODISCARD
-  inline string
+  _GLIBCXX_TO_STRING_CONSTEXPR string
   to_string(unsigned long long __val)
   {
     const auto __len = __detail::__to_chars_len(__val);
@@ -4922,32 +4928,32 @@
 #pragma GCC diagnostic pop
 
   _GLIBCXX_NODISCARD
-  inline wstring
+  _GLIBCXX_TO_STRING_CONSTEXPR wstring
   to_wstring(int __val)
   { return std::__to_wstring_numeric(std::to_string(__val)); }
 
   _GLIBCXX_NODISCARD
-  inline wstring
+  _GLIBCXX_TO_STRING_CONSTEXPR wstring
   to_wstring(unsigned __val)
   { return std::__to_wstring_numeric(std::to_string(__val)); }
 
   _GLIBCXX_NODISCARD
-  inline wstring
+  _GLIBCXX_TO_STRING_CONSTEXPR wstring
   to_wstring(long __val)
   { return std::__to_wstring_numeric(std::to_string(__val)); }
 
   _GLIBCXX_NODISCARD
-  inline wstring
+  _GLIBCXX_TO_STRING_CONSTEXPR wstring
   to_wstring(unsigned long __val)
   { return std::__to_wstring_numeric(std::to_string(__val)); }
 
   _GLIBCXX_NODISCARD
-  inline wstring
+  _GLIBCXX_TO_STRING_CONSTEXPR wstring
   to_wstring(long long __val)
   { return std::__to_wstring_numeric(std::to_string(__val)); }
 
   _GLIBCXX_NODISCARD
-  inline wstring
+  _GLIBCXX_TO_STRING_CONSTEXPR wstring
   to_wstring(unsigned long long __val)
   { return std::__to_wstring_numeric(std::to_string(__val)); }
 
@@ -4968,6 +4974,7 @@
   { return std::__to_wstring_numeric(std::to_string(__val)); }
 #endif
 #endif // _GLIBCXX_USE_WCHAR_T
+#undef _GLIBCXX_TO_STRING_CONSTEXPR
 
 _GLIBCXX_END_NAMESPACE_CXX11
 _GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 97f7d68..ea85607 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1398,6 +1398,14 @@
 
 ftms = {
   name = constexpr_string;
+  // 202511 LWG4531 Should there be a feature-test macro update for constexpr std::to_(w)string?
+  //        P3391R2 constexpr std::format
+  values = {
+    v = 202511;
+    cxxmin = 26;
+    hosted = yes;
+    cxx11abi = yes;
+  };
   values = {
     v = 201907;
     cxxmin = 20;
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 33eecbfb..59d5d92 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -1542,7 +1542,12 @@
 #undef __glibcxx_want_constexpr_flat_set
 
 #if !defined(__cpp_lib_constexpr_string)
-# if (__cplusplus >= 202002L) && _GLIBCXX_USE_CXX11_ABI && _GLIBCXX_HOSTED && (defined(__glibcxx_is_constant_evaluated))
+# if (__cplusplus >  202302L) && _GLIBCXX_USE_CXX11_ABI && _GLIBCXX_HOSTED
+#  define __glibcxx_constexpr_string 202511L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_constexpr_string)
+#   define __cpp_lib_constexpr_string 202511L
+#  endif
+# elif (__cplusplus >= 202002L) && _GLIBCXX_USE_CXX11_ABI && _GLIBCXX_HOSTED && (defined(__glibcxx_is_constant_evaluated))
 #  define __glibcxx_constexpr_string 201907L
 #  if defined(__glibcxx_want_all) || defined(__glibcxx_want_constexpr_string)
 #   define __cpp_lib_constexpr_string 201907L
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/constexpr.cc b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/constexpr.cc
index 7822c89..34abfe6 100644
--- a/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/constexpr.cc
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/constexpr.cc
@@ -6,8 +6,8 @@
 
 #ifndef __cpp_lib_constexpr_string
 # error "Feature-test macro for constexpr std::string missing in <string>"
-#elif __cpp_lib_constexpr_string != 201907L
-# error "Feature-test macro for constexpr std::string has wrong value in <string>"
+#elif __cpp_lib_constexpr_string < 201907L
+# error "Feature-test macro for constexpr std::string has too small value in <string>"
 #endif
 
 #include <testsuite_hooks.h>
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc
index 44c8391..59d5ee2 100644
--- a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/constexpr.cc
@@ -6,8 +6,8 @@
 
 #ifndef __cpp_lib_constexpr_string
 # error "Feature-test macro for constexpr std::string missing in <string>"
-#elif __cpp_lib_constexpr_string != 201907L
-# error "Feature-test macro for constexpr std::string has wrong value in <string>"
+#elif __cpp_lib_constexpr_string < 201907L
+# error "Feature-test macro for constexpr std::string too small value in <string>"
 #endif
 
 #include <testsuite_hooks.h>
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string_constexpr.cc b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string_constexpr.cc
new file mode 100644
index 0000000..1b5a616
--- /dev/null
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string_constexpr.cc
@@ -0,0 +1,64 @@
+// { dg-do compile { target c++26 } }
+// { dg-require-effective-target cxx11_abi }
+
+#include <string>
+#include <type_traits>
+#include <testsuite_hooks.h>
+
+template<typename T>
+constexpr void
+test()
+{
+  using namespace std;
+  string res;
+  T value;
+
+  value = 0;
+  res = to_string(value);
+  VERIFY( res == "0" );
+
+  value = 1;
+  res = to_string(value);
+  VERIFY( res == "1" );
+
+  value = 10;
+  res = to_string(value);
+  VERIFY( res == "10" );
+  
+  value = 3000;
+  res = to_string(value);
+  VERIFY( res == "3000" );
+
+  value = 32767;
+  res = to_string(value);
+  VERIFY( res == "32767" );
+
+  if (is_unsigned_v<T>)
+    return;
+
+  value = -1;
+  res = to_string(value);
+  VERIFY( res == "-1" );
+  
+  value = -40;
+  res = to_string(value);
+  VERIFY( res == "-40" );
+  
+  value = -32768;
+  res = to_string(value);
+  VERIFY( res == "-32768" );
+}
+
+constexpr bool
+test_all()
+{
+  test<int>();
+  test<unsigned int>();
+  test<long>();
+  test<unsigned long>();
+  test<long long>();
+  test<unsigned long long>();
+  return true;
+}
+
+static_assert(test_all());
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring_constexpr.cc b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring_constexpr.cc
new file mode 100644
index 0000000..f884fc6
--- /dev/null
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring_constexpr.cc
@@ -0,0 +1,64 @@
+// { dg-do compile { target c++26 } }
+// { dg-require-effective-target cxx11_abi }
+
+#include <string>
+#include <type_traits>
+#include <testsuite_hooks.h>
+
+template<typename T>
+constexpr void
+test()
+{
+  using namespace std;
+  wstring res;
+  T value;
+
+  value = 0;
+  res = to_wstring(value);
+  VERIFY( res == L"0" );
+
+  value = 1;
+  res = to_wstring(value);
+  VERIFY( res == L"1" );
+
+  value = 10;
+  res = to_wstring(value);
+  VERIFY( res == L"10" );
+  
+  value = 3000;
+  res = to_wstring(value);
+  VERIFY( res == L"3000" );
+
+  value = 32767;
+  res = to_wstring(value);
+  VERIFY( res == L"32767" );
+
+  if (is_unsigned_v<T>)
+    return;
+
+  value = -1;
+  res = to_wstring(value);
+  VERIFY( res == L"-1" );
+  
+  value = -40;
+  res = to_wstring(value);
+  VERIFY( res == L"-40" );
+  
+  value = -32768;
+  res = to_wstring(value);
+  VERIFY( res == L"-32768" );
+}
+
+constexpr bool
+test_all()
+{
+  test<int>();
+  test<unsigned int>();
+  test<long>();
+  test<unsigned long>();
+  test<long long>();
+  test<unsigned long long>();
+  return true;
+}
+
+static_assert(test_all());
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/version.cc b/libstdc++-v3/testsuite/21_strings/basic_string/version.cc
index 71dd4df..b0cdf04 100644
--- a/libstdc++-v3/testsuite/21_strings/basic_string/version.cc
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/version.cc
@@ -25,3 +25,15 @@
 #  endif
 # endif
 #endif
+
+#if __cplusplus > 202302L
+# if _GLIBCXX_USE_CXX11_ABI
+#  if __cpp_lib_constexpr_string != 202511L
+#   error "Feature-test macro for constexpr std::string has wrong value for C++20 in <version>"
+#  endif
+# else // COW strings
+#  if __cpp_lib_constexpr_string != 201811L
+#   error "Feature-test macro for constexpr std::string has wrong value for C++20 in <version>"
+#  endif
+# endif
+#endif