Skip to content

WIP - [libc++][string] P2587R3: to_string or not to_string #78100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

H-G-Hristov
Copy link
Contributor

@H-G-Hristov H-G-Hristov commented Jan 14, 2024

Implements: https://wg21.link/P2587R3

Closes #105359

NOTE: The PR is on hold because of #78100 (comment)

std::format for long double is still not working properly when long double is not an IEEE-754 double type.

Copy link

github-actions bot commented Jan 14, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@H-G-Hristov H-G-Hristov changed the title [libc++][string] P2587R3: to_string or not to_string WIP: [libc++][string] P2587R3: to_string or not to_string Jan 14, 2024
@H-G-Hristov H-G-Hristov force-pushed the hgh/libcxx/P2587R3-Artihmetic-overloads-of-to_string branch 2 times, most recently from 7453327 to 607f643 Compare January 14, 2024 17:15
@H-G-Hristov
Copy link
Contributor Author

H-G-Hristov commented Jan 20, 2024

Some notes:

  • the code is in the dylib. Removing that breaks Apple backdeployment. (The dylib is always built with the same C++ version).

  • std::format's implementation of long double is incorrect. Our to_chars implementation is donated by the MSVC team. On Windows long double and double use the same underlying type. So the output accuracy of long double is not correct on platforms where taht is not the case.

  • Move the implementation to the headers.

  • Keep the old symbols for backwards compatibility.

@H-G-Hristov H-G-Hristov changed the title WIP: [libc++][string] P2587R3: to_string or not to_string WIP - [libc++][string] P2587R3: to_string or not to_string Mar 1, 2024
@Zingam Zingam added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Mar 1, 2024
@llvmbot
Copy link
Member

llvmbot commented Mar 1, 2024

@llvm/pr-subscribers-libcxx

Author: Hristo Hristov (H-G-Hristov)

Changes

Implements: https://wg21.link/P2587R3


Patch is 25.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/78100.diff

9 Files Affected:

  • (modified) libcxx/docs/FeatureTestMacroTable.rst (+1-1)
  • (modified) libcxx/docs/Status/Cxx2cPapers.csv (+1-1)
  • (modified) libcxx/include/version (+1-1)
  • (modified) libcxx/src/string.cpp (+42-9)
  • (modified) libcxx/test/std/language.support/support.limits/support.limits.general/string.version.compile.pass.cpp (+10-22)
  • (modified) libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp (+10-22)
  • (modified) libcxx/test/std/strings/string.conversions/to_string.pass.cpp (+130-1)
  • (modified) libcxx/test/std/strings/string.conversions/to_wstring.pass.cpp (+130-1)
  • (modified) libcxx/utils/generate_feature_test_macro_components.py (-1)
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());
     ...
[truncated]

ldionne pushed a commit that referenced this pull request May 28, 2024
…to_string` as C++26 (#93255)

[P2845R8](https://wg21.link/P2845R8) "Formatting of
`std::filesystem::path`" and [P2587R3](https://wg21.link/P2587R3)
"`to_string` or not `to_string`" are C++26 features, so they should be
marked accordingly in `generate_feature_test_macro_components.py`.

I verified that without my changes, running the script produced no
edits. Then with my changes, I ran the script to regenerate all files,
with no other manual edits.

Found while running libc++'s tests with MSVC's STL, which noticed this
because it's currently a C++23-only implementation.

Note that @H-G-Hristov has a draft implementation of P2587R3: #78100
vg0204 pushed a commit to vg0204/llvm-project that referenced this pull request May 29, 2024
…to_string` as C++26 (llvm#93255)

[P2845R8](https://wg21.link/P2845R8) "Formatting of
`std::filesystem::path`" and [P2587R3](https://wg21.link/P2587R3)
"`to_string` or not `to_string`" are C++26 features, so they should be
marked accordingly in `generate_feature_test_macro_components.py`.

I verified that without my changes, running the script produced no
edits. Then with my changes, I ran the script to regenerate all files,
with no other manual edits.

Found while running libc++'s tests with MSVC's STL, which noticed this
because it's currently a C++23-only implementation.

Note that @H-G-Hristov has a draft implementation of P2587R3: llvm#78100
@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Mar 11, 2025

Can I take this with your test cases reused?

I have some thoughts on separately compiled functions, but I'm a bit too unfamiliar with test cases.

@mordante
Copy link
Member

Can I take this with your test cases reused?

I have some thoughts on separately compiled functions, but I'm a bit too unfamiliar with test cases.

IMO we shouldn't do this at the moment. std::format for long double is still not working properly when long double is not an IEEE-754 double type.

@H-G-Hristov
Copy link
Contributor Author

H-G-Hristov commented Mar 12, 2025

Can I take this with your test cases reused?

I have some thoughts on separately compiled functions, but I'm a bit too unfamiliar with test cases.

@frederick-vs-ja In case this PR is open for business again. Please feel free to take it if you like.

I'll resolve the merge conflicts and I'll make the PR up-to-date now.

@H-G-Hristov H-G-Hristov force-pushed the hgh/libcxx/P2587R3-Artihmetic-overloads-of-to_string branch 7 times, most recently from 928351c to 49b2559 Compare March 14, 2025 06:03
@philnik777
Copy link
Contributor

IMO we should discuss whether we can just backport this to all standards modes. The committee seems to be fine with this being hypothetically breaking after all. That would allow us to just update the definition in the dylib and have the FTM set when a dylib that is new enough is used instead of introducing new symbols. (FWiW I also don't understand why you're introducing both an underscored version and an inline namespace)

@H-G-Hristov
Copy link
Contributor Author

IMO we should discuss whether we can just backport this to all standards modes. The committee seems to be fine with this being hypothetically breaking after all. That would allow us to just update the definition in the dylib and have the FTM set when a dylib that is new enough is used instead of introducing new symbols. (FWiW I also don't understand why you're introducing both an underscored version and an inline namespace)

Thanks. I guess by the time when we could implement this, the final version will look different.

_LIBCPP_EXPORTED_FROM_ABI string __to_string(double __val);
_LIBCPP_EXPORTED_FROM_ABI string __to_string(long double __val);

inline _LIBCPP_HIDE_FROM_ABI string to_string(float __val) { return std::__to_string(__val); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use to_chars here to avoid changing ABI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See also my branch: https://github.com/frederick-vs-ja/llvm-project/tree/p2587r3. Do you think my approach viable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frederick-vs-ja@78f270a - this looks like a better alternative to my experiments in implementing this.

BTW I wanted to get the CI green with what I have so far. I still haven't.

Would you like to update this PR with your changes or wait for whenever we can/decide to pursue this paper further or create a new PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it seems it may take quite a while before to_chars will be done I don't think we need to spend time now to fix the CI, this will be outdated once we can pursue this patch.

That is unless somebody else volunteers to implement to_chars long double. It is on my TODO list, but not at a high priority. Realistically I don't expect to work on it this year.

@H-G-Hristov H-G-Hristov force-pushed the hgh/libcxx/P2587R3-Artihmetic-overloads-of-to_string branch from 3b59c59 to df6fd5b Compare March 14, 2025 10:50
@H-G-Hristov H-G-Hristov force-pushed the hgh/libcxx/P2587R3-Artihmetic-overloads-of-to_string branch from df6fd5b to c6c25dc Compare March 14, 2025 11:02
@Zingam Zingam added the c++26 label Mar 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++26 libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

P2587R3: to_string or not to_string
6 participants