[libcxx-commits] [libcxx] WIP: [libc++][string] P2587R3: `to_string` or not `to_string` (PR #78100)

Hristo Hristov via libcxx-commits libcxx-commits at lists.llvm.org
Sun Jan 14 09:16:05 PST 2024


https://github.com/H-G-Hristov updated https://github.com/llvm/llvm-project/pull/78100

>From 607f6432dfff8337c85156b8012a21ee9f90e27b Mon Sep 17 00:00:00 2001
From: Zingam <zingam at outlook.com>
Date: Wed, 10 Jan 2024 15:55:44 +0200
Subject: [PATCH] [libc++][string] P2587R3: `to_string` or not `to_string`

Implements: https://wg21.link/P2587R3
- https://eel.is/c++draft/string.conversions
- https://eel.is/c++draft/string.classes
---
 libcxx/docs/FeatureTestMacroTable.rst         |   2 +-
 libcxx/docs/Status/Cxx2cPapers.csv            |   2 +-
 libcxx/include/version                        |   2 +-
 libcxx/src/string.cpp                         |  51 +++++--
 .../string.version.compile.pass.cpp           |  32 ++---
 .../version.version.compile.pass.cpp          |  32 ++---
 .../string.conversions/to_string.pass.cpp     | 131 +++++++++++++++++-
 .../string.conversions/to_wstring.pass.cpp    | 131 +++++++++++++++++-
 .../generate_feature_test_macro_components.py |   1 -
 9 files changed, 325 insertions(+), 59 deletions(-)

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 893a3b13ca06e0..d17a056e688c02 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -380,7 +380,7 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_string_resize_and_overwrite``           ``202110L``
     --------------------------------------------------- -----------------
-    ``__cpp_lib_to_string``                             *unimplemented*
+    ``__cpp_lib_to_string``                             ``202306L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_to_underlying``                         ``202102L``
     --------------------------------------------------- -----------------
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index 5701717f39766c..6e9ca8f4e890a1 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -1,7 +1,7 @@
 "Paper #","Group","Paper Name","Meeting","Status","First released version","Labels"
 "`P2497R0 <https://wg21.link/P2497R0>`__","LWG","Testing for success or failure of ``<charconv>`` functions","Varna June 2023","|Complete|","18.0",""
 "`P2592R3 <https://wg21.link/P2592R3>`__","LWG","Hashing support for ``std::chrono`` value classes","Varna June 2023","","",""
-"`P2587R3 <https://wg21.link/P2587R3>`__","LWG","``to_string`` or not ``to_string``","Varna June 2023","","","|format|"
+"`P2587R3 <https://wg21.link/P2587R3>`__","LWG","``to_string`` or not ``to_string``","Varna June 2023","|Complete|","18.0","|format|"
 "`P2562R1 <https://wg21.link/P2562R1>`__","LWG","``constexpr`` Stable Sorting","Varna June 2023","","",""
 "`P2545R4 <https://wg21.link/P2545R4>`__","LWG","Read-Copy Update (RCU)","Varna June 2023","","",""
 "`P2530R3 <https://wg21.link/P2530R3>`__","LWG","Hazard Pointers for C++26","Varna June 2023","","",""
diff --git a/libcxx/include/version b/libcxx/include/version
index c96647894dce63..efb7f56f74966d 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -473,7 +473,7 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 # define __cpp_lib_stdatomic_h                          202011L
 # define __cpp_lib_string_contains                      202011L
 # define __cpp_lib_string_resize_and_overwrite          202110L
-// # define __cpp_lib_to_string                            202306L
+# define __cpp_lib_to_string                            202306L
 # define __cpp_lib_to_underlying                        202102L
 // # define __cpp_lib_tuple_like                           202207L
 # define __cpp_lib_unreachable                          202202L
diff --git a/libcxx/src/string.cpp b/libcxx/src/string.cpp
index cf07b3ef1ef270..630f76d4d878c5 100644
--- a/libcxx/src/string.cpp
+++ b/libcxx/src/string.cpp
@@ -10,6 +10,7 @@
 #include <cerrno>
 #include <charconv>
 #include <cstdlib>
+#include <format>
 #include <limits>
 #include <stdexcept>
 #include <string>
@@ -247,6 +248,36 @@ long double stold(const wstring& str, size_t* idx) { return as_float<long double
 
 // to_string
 
+#if _LIBCPP_STD_VER >= 26
+
+string to_string(int val) { return std::format("{}", val); }
+string to_string(long val) { return std::format("{}", val); }
+string to_string(long long val) { return std::format("{}", val); }
+string to_string(unsigned val) { return std::format("{}", val); }
+string to_string(unsigned long val) { return std::format("{}", val); }
+string to_string(unsigned long long val) { return std::format("{}", val); }
+
+#  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+wstring to_wstring(int val) { return std::format(L"{}", val); }
+wstring to_wstring(long val) { return std::format(L"{}", val); }
+wstring to_wstring(long long val) { return std::format(L"{}", val); }
+wstring to_wstring(unsigned val) { return std::format(L"{}", val); }
+wstring to_wstring(unsigned long val) { return std::format(L"{}", val); }
+wstring to_wstring(unsigned long long val) { return std::format(L"{}", val); }
+#  endif
+
+string to_string(float val) { return std::format("{}", val); }
+string to_string(double val) { return std::format("{}", val); }
+string to_string(long double val) { return std::format("{}", val); }
+
+#  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+wstring to_wstring(float val) { return std::format(L"{}", val); }
+wstring to_wstring(double val) { return std::format(L"{}", val); }
+wstring to_wstring(long double val) { return std::format(L"{}", val); }
+#  endif
+
+#else
+
 namespace {
 
 // as_string
@@ -283,7 +314,7 @@ struct initial_string<string> {
   }
 };
 
-#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+#  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
 template <>
 struct initial_string<wstring> {
   wstring operator()() const {
@@ -296,13 +327,13 @@ struct initial_string<wstring> {
 typedef int (*wide_printf)(wchar_t* __restrict, size_t, const wchar_t* __restrict, ...);
 
 inline wide_printf get_swprintf() {
-#  ifndef _LIBCPP_MSVCRT
+#    ifndef _LIBCPP_MSVCRT
   return swprintf;
-#  else
+#    else
   return static_cast<int(__cdecl*)(wchar_t* __restrict, size_t, const wchar_t* __restrict, ...)>(_snwprintf);
-#  endif
+#    endif
 }
-#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
+#  endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
 
 template <typename S, typename V>
 S i_to_string(V v) {
@@ -325,23 +356,25 @@ 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); }
 
-#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+#  ifndef _LIBCPP_HAS_NO_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); }
-#endif
+#  endif
 
 string to_string(float val) { return as_string(snprintf, initial_string< string>()(), "%f", val); }
 string to_string(double val) { return as_string(snprintf, initial_string< string>()(), "%f", val); }
 string to_string(long double val) { return as_string(snprintf, initial_string< string>()(), "%Lf", val); }
 
-#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+#  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
 wstring to_wstring(float val) { return as_string(get_swprintf(), initial_string<wstring>()(), L"%f", val); }
 wstring to_wstring(double val) { return as_string(get_swprintf(), initial_string<wstring>()(), L"%f", val); }
 wstring to_wstring(long double val) { return as_string(get_swprintf(), initial_string<wstring>()(), L"%Lf", val); }
-#endif
+#  endif
+
+#endif // _LIBCPP_STD_VER >= 26
 
 _LIBCPP_END_NAMESPACE_STD
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 b5770f8fbe65d3..b4e8927cd113df 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
@@ -364,17 +364,11 @@
 #   error "__cpp_lib_string_view should have the value 201803L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_to_string
-#     error "__cpp_lib_to_string should be defined in c++23"
-#   endif
-#   if __cpp_lib_to_string != 202306L
-#     error "__cpp_lib_to_string should have the value 202306L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_to_string
-#     error "__cpp_lib_to_string should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_to_string
+#   error "__cpp_lib_to_string should be defined in c++23"
+# endif
+# if __cpp_lib_to_string != 202306L
+#   error "__cpp_lib_to_string should have the value 202306L in c++23"
 # endif
 
 #elif TEST_STD_VER > 23
@@ -462,17 +456,11 @@
 #   error "__cpp_lib_string_view should have the value 201803L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_to_string
-#     error "__cpp_lib_to_string should be defined in c++26"
-#   endif
-#   if __cpp_lib_to_string != 202306L
-#     error "__cpp_lib_to_string should have the value 202306L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_to_string
-#     error "__cpp_lib_to_string should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_to_string
+#   error "__cpp_lib_to_string should be defined in c++26"
+# endif
+# if __cpp_lib_to_string != 202306L
+#   error "__cpp_lib_to_string should have the value 202306L in c++26"
 # endif
 
 #endif // TEST_STD_VER > 23
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 d5a0839b30f824..e01a781b2f0682 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
@@ -5706,17 +5706,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_to_string
-#     error "__cpp_lib_to_string should be defined in c++23"
-#   endif
-#   if __cpp_lib_to_string != 202306L
-#     error "__cpp_lib_to_string should have the value 202306L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_to_string
-#     error "__cpp_lib_to_string should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_to_string
+#   error "__cpp_lib_to_string should be defined in c++23"
+# endif
+# if __cpp_lib_to_string != 202306L
+#   error "__cpp_lib_to_string should have the value 202306L in c++23"
 # endif
 
 # ifndef __cpp_lib_to_underlying
@@ -7474,17 +7468,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_to_string
-#     error "__cpp_lib_to_string should be defined in c++26"
-#   endif
-#   if __cpp_lib_to_string != 202306L
-#     error "__cpp_lib_to_string should have the value 202306L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_to_string
-#     error "__cpp_lib_to_string should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_to_string
+#   error "__cpp_lib_to_string should be defined in c++26"
+# endif
+# if __cpp_lib_to_string != 202306L
+#   error "__cpp_lib_to_string should have the value 202306L in c++26"
 # endif
 
 # ifndef __cpp_lib_to_underlying
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 4731a072e89108..87ab89c39ffe7f 100644
--- a/libcxx/test/std/strings/string.conversions/to_string.pass.cpp
+++ b/libcxx/test/std/strings/string.conversions/to_string.pass.cpp
@@ -18,8 +18,9 @@
 // string to_string(double val);
 // string to_string(long double val);
 
-#include <string>
 #include <cassert>
+#include <format>
+#include <string>
 #include <limits>
 
 #include "parse_integer.h"
@@ -32,29 +33,44 @@ void test_signed() {
     assert(s.size() == 1);
     assert(s[s.size()] == 0);
     assert(s == "0");
+#if TEST_STD_VER >= 26
+    assert(s == std::format("{}", T(0)));
+#endif
   }
   {
     std::string s = std::to_string(T(12345));
     assert(s.size() == 5);
     assert(s[s.size()] == 0);
     assert(s == "12345");
+#if TEST_STD_VER >= 26
+    assert(s == std::format("{}", T(12345)));
+#endif
   }
   {
     std::string s = std::to_string(T(-12345));
     assert(s.size() == 6);
     assert(s[s.size()] == 0);
     assert(s == "-12345");
+#if TEST_STD_VER >= 26
+    assert(s == std::format("{}", T(-12345)));
+#endif
   }
   {
     std::string s = std::to_string(std::numeric_limits<T>::max());
     assert(s.size() == std::numeric_limits<T>::digits10 + 1);
     T t = parse_integer<T>(s);
     assert(t == std::numeric_limits<T>::max());
+#if TEST_STD_VER >= 26
+    assert(s == std::format("{}", std::numeric_limits<T>::max()));
+#endif
   }
   {
     std::string s = std::to_string(std::numeric_limits<T>::min());
     T t           = parse_integer<T>(s);
     assert(t == std::numeric_limits<T>::min());
+#if TEST_STD_VER >= 26
+    assert(s == std::format("{}", std::numeric_limits<T>::min()));
+#endif
   }
 }
 
@@ -65,18 +81,27 @@ void test_unsigned() {
     assert(s.size() == 1);
     assert(s[s.size()] == 0);
     assert(s == "0");
+#if TEST_STD_VER >= 26
+    assert(s == std::format("{}", T(0)));
+#endif
   }
   {
     std::string s = std::to_string(T(12345));
     assert(s.size() == 5);
     assert(s[s.size()] == 0);
     assert(s == "12345");
+#if TEST_STD_VER >= 26
+    assert(s == std::format("{}", T(12345)));
+#endif
   }
   {
     std::string s = std::to_string(std::numeric_limits<T>::max());
     assert(s.size() == std::numeric_limits<T>::digits10 + 1);
     T t = parse_integer<T>(s);
     assert(t == std::numeric_limits<T>::max());
+#if TEST_STD_VER >= 26
+    assert(s == std::format("{}", std::numeric_limits<T>::max()));
+#endif
   }
 }
 
@@ -84,24 +109,125 @@ template <class T>
 void test_float() {
   {
     std::string s = std::to_string(T(0));
+#if TEST_STD_VER < 26
     assert(s.size() == 8);
     assert(s[s.size()] == 0);
     assert(s == "0.000000");
+#else
+    std::string f = std::format("{}", T(0));
+    assert(s == f);
+    assert(s == "0");
+#endif
   }
   {
     std::string s = std::to_string(T(12345));
+#if TEST_STD_VER < 26
     assert(s.size() == 12);
     assert(s[s.size()] == 0);
     assert(s == "12345.000000");
+#else
+    std::string f = std::format("{}", T(12345));
+    assert(s == f);
+    assert(s == "12345");
+#endif
   }
   {
     std::string s = std::to_string(T(-12345));
+#if TEST_STD_VER < 26
     assert(s.size() == 13);
     assert(s[s.size()] == 0);
     assert(s == "-12345.000000");
+#else
+    std::string f = std::format("{}", T(-12345));
+    assert(s == f);
+    assert(s == "-12345");
+#endif
+  }
+
+#if TEST_STD_VER >= 26
+  {
+    std::string s = std::to_string(T(90.84));
+    std::string f = std::format("{}", T(90.84));
+    assert(s == f);
+    assert(s == "90.84");
   }
+  {
+    std::string s = std::to_string(T(-90.84));
+    std::string f = std::format("{}", T(-90.84));
+    assert(s == f);
+    assert(s == "-90.84");
+  }
+#endif
+}
+
+#if TEST_STD_VER >= 26
+
+template <class T>
+void test_float_with_locale(const char* locale, T inputValue, const char* expectedValue) {
+  setlocale(LC_ALL, locale);
+
+  std::string s = std::to_string(inputValue);
+  std::string f = std::format("{}", inputValue);
+  assert(s == f);
+  assert(s == expectedValue);
 }
 
+void test_float_with_locale() {
+  // Locale "C"
+
+  test_float_with_locale<float>("C", 0.9084, "0.9084");
+  test_float_with_locale<double>("C", 0.9084, "0.9084");
+  test_float_with_locale<long double>("C", 0.9084, "0.9084");
+
+  test_float_with_locale<float>("C", -0.9084, "-0.9084");
+  test_float_with_locale<double>("C", -0.9084, "-0.9084");
+  test_float_with_locale<long double>("C", -0.9084, "-0.9084");
+
+  test_float_with_locale<float>("C", 1e-7, "1e-07");
+  test_float_with_locale<double>("C", 1e-7, "1e-07");
+  test_float_with_locale<long double>("C", 1e-7, "1e-07");
+
+  test_float_with_locale<float>("C", -1e-7, "-1e-07");
+  test_float_with_locale<double>("C", -1e-7, "-1e-07");
+  test_float_with_locale<long double>("C", -1e-7, "-1e-07");
+
+  test_float_with_locale<float>("C", 1.7976931348623157e+308, "inf");
+  test_float_with_locale<double>("C", 1.7976931348623157e+308, "1.7976931348623157e+308");
+  test_float_with_locale<long double>("C", 1.7976931348623157e+308, "1.7976931348623157e+308");
+
+  test_float_with_locale<float>("C", -1.7976931348623157e+308, "-inf");
+  test_float_with_locale<double>("C", -1.7976931348623157e+308, "-1.7976931348623157e+308");
+  test_float_with_locale<long double>("C", -1.7976931348623157e+308, "-1.7976931348623157e+308");
+
+  // Locale "uk_UA.UTF-8"
+
+  test_float_with_locale<float>("uk_UA.UTF-8", 0.9084, "0.9084");
+  test_float_with_locale<double>("uk_UA.UTF-8", 0.9084, "0.9084");
+  test_float_with_locale<double>("uk_UA.UTF-8", 0.9084, "0.9084");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", -0.9084, "-0.9084");
+  test_float_with_locale<double>("uk_UA.UTF-8", -0.9084, "-0.9084");
+  test_float_with_locale<long double>("uk_UA.UTF-8", -0.9084, "-0.9084");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", 1e-7, "1e-07");
+  test_float_with_locale<double>("uk_UA.UTF-8", 1e-7, "1e-07");
+  test_float_with_locale<long double>("uk_UA.UTF-8", 1e-7, "1e-07");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", -1e-7, "-1e-07");
+  test_float_with_locale<double>("uk_UA.UTF-8", -1e-7, "-1e-07");
+  test_float_with_locale<long double>("uk_UA.UTF-8", -1e-7, "-1e-07");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", 1.7976931348623157e+308, "inf");
+  test_float_with_locale<double>("uk_UA.UTF-8", 1.7976931348623157e+308, "1.7976931348623157e+308");
+  test_float_with_locale<long double>("uk_UA.UTF-8", 1.7976931348623157e+308, "1.7976931348623157e+308");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", -1.7976931348623157e+308, "-inf");
+  test_float_with_locale<double>("uk_UA.UTF-8", -1.7976931348623157e+308, "-1.7976931348623157e+308");
+  test_float_with_locale<long double>("uk_UA.UTF-8", -1.7976931348623157e+308, "-1.7976931348623157e+308");
+}
+
+#endif
+
 int main(int, char**) {
   test_signed<int>();
   test_signed<long>();
@@ -112,6 +238,9 @@ int main(int, char**) {
   test_float<float>();
   test_float<double>();
   test_float<long double>();
+#if TEST_STD_VER >= 26
+  test_float_with_locale();
+#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 fff5ede848b57a..6c3a5825c43aa0 100644
--- a/libcxx/test/std/strings/string.conversions/to_wstring.pass.cpp
+++ b/libcxx/test/std/strings/string.conversions/to_wstring.pass.cpp
@@ -20,8 +20,9 @@
 // wstring to_wstring(double val);
 // wstring to_wstring(long double val);
 
-#include <string>
 #include <cassert>
+#include <format>
+#include <string>
 #include <limits>
 
 #include "parse_integer.h"
@@ -34,29 +35,44 @@ void test_signed() {
     assert(s.size() == 1);
     assert(s[s.size()] == 0);
     assert(s == L"0");
+#if TEST_STD_VER >= 26
+    assert(s == std::format(L"{}", T(0)));
+#endif
   }
   {
     std::wstring s = std::to_wstring(T(12345));
     assert(s.size() == 5);
     assert(s[s.size()] == 0);
     assert(s == L"12345");
+#if TEST_STD_VER >= 26
+    assert(s == std::format(L"{}", T(12345)));
+#endif
   }
   {
     std::wstring s = std::to_wstring(T(-12345));
     assert(s.size() == 6);
     assert(s[s.size()] == 0);
     assert(s == L"-12345");
+#if TEST_STD_VER >= 26
+    assert(s == std::format(L"{}", T(-12345)));
+#endif
   }
   {
     std::wstring s = std::to_wstring(std::numeric_limits<T>::max());
     assert(s.size() == std::numeric_limits<T>::digits10 + 1);
     T t = parse_integer<T>(s);
     assert(t == std::numeric_limits<T>::max());
+#if TEST_STD_VER >= 26
+    assert(s == std::format(L"{}", T(std::numeric_limits<T>::max())));
+#endif
   }
   {
     std::wstring s = std::to_wstring(std::numeric_limits<T>::min());
     T t            = parse_integer<T>(s);
     assert(t == std::numeric_limits<T>::min());
+#if TEST_STD_VER >= 26
+    assert(s == std::format(L"{}", T(std::numeric_limits<T>::min())));
+#endif
   }
 }
 
@@ -67,18 +83,27 @@ void test_unsigned() {
     assert(s.size() == 1);
     assert(s[s.size()] == 0);
     assert(s == L"0");
+#if TEST_STD_VER >= 26
+    assert(s == std::format(L"{}", T(0)));
+#endif
   }
   {
     std::wstring s = std::to_wstring(T(12345));
     assert(s.size() == 5);
     assert(s[s.size()] == 0);
     assert(s == L"12345");
+#if TEST_STD_VER >= 26
+    assert(s == std::format(L"{}", T(12345)));
+#endif
   }
   {
     std::wstring s = std::to_wstring(std::numeric_limits<T>::max());
     assert(s.size() == std::numeric_limits<T>::digits10 + 1);
     T t = parse_integer<T>(s);
     assert(t == std::numeric_limits<T>::max());
+#if TEST_STD_VER >= 26
+    assert(s == std::format(L"{}", T(std::numeric_limits<T>::max())));
+#endif
   }
 }
 
@@ -86,24 +111,125 @@ template <class T>
 void test_float() {
   {
     std::wstring s = std::to_wstring(T(0));
+#if TEST_STD_VER < 26
     assert(s.size() == 8);
     assert(s[s.size()] == 0);
     assert(s == L"0.000000");
+#else
+    std::wstring f = std::format(L"{}", T(0));
+    assert(s == f);
+    assert(s == L"0");
+#endif
   }
   {
     std::wstring s = std::to_wstring(T(12345));
+#if TEST_STD_VER < 26
     assert(s.size() == 12);
     assert(s[s.size()] == 0);
     assert(s == L"12345.000000");
+#else
+    std::wstring f = std::format(L"{}", T(12345));
+    assert(s == f);
+    assert(s == L"12345");
+#endif
   }
   {
     std::wstring s = std::to_wstring(T(-12345));
+#if TEST_STD_VER < 26
     assert(s.size() == 13);
     assert(s[s.size()] == 0);
     assert(s == L"-12345.000000");
+#else
+    std::wstring f = std::format(L"{}", T(-12345));
+    assert(s == f);
+    assert(s == L"-12345");
+#endif
+  }
+
+#if TEST_STD_VER >= 26
+  {
+    std::wstring s = std::to_wstring(T(90.84));
+    std::wstring f = std::format(L"{}", T(90.84));
+    assert(s == f);
+    assert(s == L"90.84");
   }
+  {
+    std::wstring s = std::to_wstring(T(-90.84));
+    std::wstring f = std::format(L"{}", T(-90.84));
+    assert(s == f);
+    assert(s == L"-90.84");
+  }
+#endif
+}
+
+#if TEST_STD_VER >= 26
+
+template <class T>
+void test_float_with_locale(const char* locale, T inputValue, const wchar_t* expectedValue) {
+  setlocale(LC_ALL, locale);
+
+  std::wstring s = std::to_wstring(inputValue);
+  std::wstring f = std::format(L"{}", inputValue);
+  assert(s == f);
+  assert(s == expectedValue);
 }
 
+void test_float_with_locale() {
+  // Locale "C"
+
+  test_float_with_locale<float>("C", 0.9084, L"0.9084");
+  test_float_with_locale<double>("C", 0.9084, L"0.9084");
+  test_float_with_locale<long double>("C", 0.9084, L"0.9084");
+
+  test_float_with_locale<float>("C", -0.9084, L"-0.9084");
+  test_float_with_locale<double>("C", -0.9084, L"-0.9084");
+  test_float_with_locale<long double>("C", -0.9084, L"-0.9084");
+
+  test_float_with_locale<float>("C", 1e-7, L"1e-07");
+  test_float_with_locale<double>("C", 1e-7, L"1e-07");
+  test_float_with_locale<long double>("C", 1e-7, L"1e-07");
+
+  test_float_with_locale<float>("C", -1e-7, L"-1e-07");
+  test_float_with_locale<double>("C", -1e-7, L"-1e-07");
+  test_float_with_locale<long double>("C", -1e-7, L"-1e-07");
+
+  test_float_with_locale<float>("C", 1.7976931348623157e+308, L"inf");
+  test_float_with_locale<double>("C", 1.7976931348623157e+308, L"1.7976931348623157e+308");
+  test_float_with_locale<long double>("C", 1.7976931348623157e+308, L"1.7976931348623157e+308");
+
+  test_float_with_locale<float>("C", -1.7976931348623157e+308, L"-inf");
+  test_float_with_locale<double>("C", -1.7976931348623157e+308, L"-1.7976931348623157e+308");
+  test_float_with_locale<long double>("C", -1.7976931348623157e+308, L"-1.7976931348623157e+308");
+
+  // Locale "uk_UA.UTF-8"
+
+  test_float_with_locale<float>("uk_UA.UTF-8", 0.9084, L"0.9084");
+  test_float_with_locale<double>("uk_UA.UTF-8", 0.9084, L"0.9084");
+  test_float_with_locale<double>("uk_UA.UTF-8", 0.9084, L"0.9084");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", -0.9084, L"-0.9084");
+  test_float_with_locale<double>("uk_UA.UTF-8", -0.9084, L"-0.9084");
+  test_float_with_locale<long double>("uk_UA.UTF-8", -0.9084, L"-0.9084");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", 1e-7, L"1e-07");
+  test_float_with_locale<double>("uk_UA.UTF-8", 1e-7, L"1e-07");
+  test_float_with_locale<long double>("uk_UA.UTF-8", 1e-7, L"1e-07");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", -1e-7, L"-1e-07");
+  test_float_with_locale<double>("uk_UA.UTF-8", -1e-7, L"-1e-07");
+  test_float_with_locale<long double>("uk_UA.UTF-8", -1e-7, L"-1e-07");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", 1.7976931348623157e+308, L"inf");
+  test_float_with_locale<double>("uk_UA.UTF-8", 1.7976931348623157e+308, L"1.7976931348623157e+308");
+  test_float_with_locale<long double>("uk_UA.UTF-8", 1.7976931348623157e+308, L"1.7976931348623157e+308");
+
+  test_float_with_locale<float>("uk_UA.UTF-8", -1.7976931348623157e+308, L"-inf");
+  test_float_with_locale<double>("uk_UA.UTF-8", -1.7976931348623157e+308, L"-1.7976931348623157e+308");
+  test_float_with_locale<long double>("uk_UA.UTF-8", -1.7976931348623157e+308, L"-1.7976931348623157e+308");
+}
+
+#endif
+
 int main(int, char**) {
   test_signed<int>();
   test_signed<long>();
@@ -114,6 +240,9 @@ int main(int, char**) {
   test_float<float>();
   test_float<double>();
   test_float<long double>();
+#if TEST_STD_VER >= 26
+  test_float_with_locale();
+#endif
 
   return 0;
 }
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 8ee92909dfa53c..2b348f0dbf1aed 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1200,7 +1200,6 @@ def add_version_header(tc):
             "name": "__cpp_lib_to_string",
             "values": {"c++23": 202306},  # P2587R3 to_string or not to_string
             "headers": ["string"],
-            "unimplemented": True,
         },
         {
             "name": "__cpp_lib_to_underlying",



More information about the libcxx-commits mailing list