public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
* [committed] libstdc++: Implement std::to_string in terms of std::format (P2587R3)
@ 2023-08-17 20:31 Jonathan Wakely
  0 siblings, 0 replies; only message in thread
From: Jonathan Wakely @ 2023-08-17 20:31 UTC (permalink / raw)
  To: libstdc++, gcc-patches

Tested x86_64-linux. Pushed to trunk.

-- >8 --

This change for C++26 affects std::to_string for floating-point
arguments, so that they should be formatted using std::format("{}", v)
instead of using sprintf. The modified specification in the standard
also affects integral arguments, but there's no observable difference
for them, and we already use std::to_chars for them anyway.

To avoid <string> depending on all of <format>, this change actually
just uses std::to_chars directly instead of using std::format. This is
equivalent, because the format spec "{}" doesn't use any of the other
features of std::format.

libstdc++-v3/ChangeLog:

	* include/bits/basic_string.h (to_string(floating-point-type)):
	Implement using std::to_chars for C++26.
	* include/bits/version.def (__cpp_lib_to_string): Define.
	* include/bits/version.h: Regenerate.
	* testsuite/21_strings/basic_string/numeric_conversions/char/dr1261.cc:
	Adjust expected result in C++26 mode.
	* testsuite/21_strings/basic_string/numeric_conversions/char/to_string.cc:
	Likewise.
	* testsuite/21_strings/basic_string/numeric_conversions/wchar_t/dr1261.cc:
	Likewise.
	* testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring.cc:
	Likewise.
	* testsuite/21_strings/basic_string/numeric_conversions/char/to_string_float.cc:
	New test.
	* testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring_float.cc:
	New test.
	* testsuite/21_strings/basic_string/numeric_conversions/version.cc:
	New test.
---
 libstdc++-v3/include/bits/basic_string.h      |  68 +++++++-
 libstdc++-v3/include/bits/version.def         |  11 ++
 libstdc++-v3/include/bits/version.h           |  11 ++
 .../numeric_conversions/char/dr1261.cc        |  11 +-
 .../numeric_conversions/char/to_string.cc     |   9 +-
 .../char/to_string_float.cc                   | 148 ++++++++++++++++++
 .../numeric_conversions/version.cc            |  18 +++
 .../numeric_conversions/wchar_t/dr1261.cc     |  11 +-
 .../numeric_conversions/wchar_t/to_wstring.cc |   9 +-
 .../wchar_t/to_wstring_float.cc               | 145 +++++++++++++++++
 10 files changed, 429 insertions(+), 12 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string_float.cc
 create mode 100644 libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/version.cc
 create mode 100644 libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring_float.cc

diff --git a/libstdc++-v3/include/bits/basic_string.h b/libstdc++-v3/include/bits/basic_string.h
index 46326d02597..f4bbf521bba 100644
--- a/libstdc++-v3/include/bits/basic_string.h
+++ b/libstdc++-v3/include/bits/basic_string.h
@@ -47,9 +47,14 @@
 # include <string_view>
 #endif
 
+#if __cplusplus > 202302L
+# include <charconv>
+#endif
+
 #define __glibcxx_want_constexpr_string
 #define __glibcxx_want_string_resize_and_overwrite
 #define __glibcxx_want_string_udls
+#define __glibcxx_want_to_string
 #include <bits/version.h>
 
 #if ! _GLIBCXX_USE_CXX11_ABI
@@ -4185,6 +4190,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
   { return std::stod(__str, __idx); }
 #endif
 
+  // _GLIBCXX_RESOLVE_LIB_DEFECTS
   // DR 1261. Insufficent overloads for to_string / to_wstring
 
   _GLIBCXX_NODISCARD
@@ -4287,7 +4293,65 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
     return __str;
   }
 
-#if _GLIBCXX_USE_C99_STDIO
+#if __cpp_lib_to_string >= 202306L
+
+  [[nodiscard]]
+  inline string
+  to_string(float __val)
+  {
+    string __str;
+    size_t __len = 15;
+    do {
+      __str.resize_and_overwrite(__len,
+				 [__val, &__len] (char* __p, size_t __n) {
+	auto [__end, __err] = std::to_chars(__p, __p + __n, __val);
+	if (__err == errc{}) [[likely]]
+	  return __end - __p;
+	__len *= 2;
+	return __p - __p;;
+      });
+    } while (__str.empty());
+    return __str;
+  }
+
+  [[nodiscard]]
+  inline string
+  to_string(double __val)
+  {
+    string __str;
+    size_t __len = 15;
+    do {
+      __str.resize_and_overwrite(__len,
+				 [__val, &__len] (char* __p, size_t __n) {
+	auto [__end, __err] = std::to_chars(__p, __p + __n, __val);
+	if (__err == errc{}) [[likely]]
+	  return __end - __p;
+	__len *= 2;
+	return __p - __p;;
+      });
+    } while (__str.empty());
+    return __str;
+  }
+
+  [[nodiscard]]
+  inline string
+  to_string(long double __val)
+  {
+    string __str;
+    size_t __len = 15;
+    do {
+      __str.resize_and_overwrite(__len,
+				 [__val, &__len] (char* __p, size_t __n) {
+	auto [__end, __err] = std::to_chars(__p, __p + __n, __val);
+	if (__err == errc{}) [[likely]]
+	  return __end - __p;
+	__len *= 2;
+	return __p - __p;;
+      });
+    } while (__str.empty());
+    return __str;
+  }
+#elif _GLIBCXX_USE_C99_STDIO
   // NB: (v)snprintf vs sprintf.
 
   _GLIBCXX_NODISCARD
@@ -4465,7 +4529,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
   to_wstring(unsigned long long __val)
   { return std::__to_wstring_numeric(std::to_string(__val)); }
 
-#if _GLIBCXX_USE_C99_STDIO
+#if __cpp_lib_to_string || _GLIBCXX_USE_C99_STDIO
   _GLIBCXX_NODISCARD
   inline wstring
   to_wstring(float __val)
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index e20dd3e3c0b..b50050440d9 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1582,6 +1582,16 @@ ftms = {
   };
 };
 
+ftms = {
+  name = to_string;
+  values = {
+    v = 202306;
+    cxxmin = 26;
+    hosted = yes;
+    extra_cond = "__glibcxx_to_chars";
+  };
+};
+
 // Standard test specifications.
 stds[97] = ">= 199711L";
 stds[03] = ">= 199711L";
@@ -1590,6 +1600,7 @@ stds[14] = ">= 201402L";
 stds[17] = ">= 201703L";
 stds[20] = ">= 202002L";
 stds[23] = ">= 202302L";
+stds[26] = ">  202302L"; // TODO: update when finalized
 
 // Local Variables:
 // compile-command: "autogen version.def"
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 7a276de70ef..8b8c70a6e53 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -1939,4 +1939,15 @@
 #endif /* !defined(__cpp_lib_string_resize_and_overwrite) && defined(__glibcxx_want_string_resize_and_overwrite) */
 #undef __glibcxx_want_string_resize_and_overwrite
 
+// from version.def line 1586
+#if !defined(__cpp_lib_to_string)
+# if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
+#  define __glibcxx_to_string 202306L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_to_string)
+#   define __cpp_lib_to_string 202306L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_to_string) && defined(__glibcxx_want_to_string) */
+#undef __glibcxx_want_to_string
+
 #undef __glibcxx_want_all
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/dr1261.cc b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/dr1261.cc
index 49998b54f63..b952c26c328 100644
--- a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/dr1261.cc
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/dr1261.cc
@@ -46,14 +46,19 @@ void test01()
   const string six(to_string(400ull));
   VERIFY( six == "400" );
 
+  string tail;
+#if __cpp_lib_to_string < 202306L
+  tail = ".000000";
+#endif
+
   const string seven(to_string(-1.0F));
-  VERIFY( seven == "-1.000000" );
+  VERIFY( seven == "-1" + tail );
 
   const string eight(to_string(2.0));
-  VERIFY( eight == "2.000000" );
+  VERIFY( eight == "2" + tail );
 
   const string nine(to_string(-4.0L));
-  VERIFY( nine == "-4.000000" );
+  VERIFY( nine == "-4" + tail );
 }
 
 int main()
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string.cc b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string.cc
index dc7b87b85f5..c770b4f9bdb 100644
--- a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string.cc
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string.cc
@@ -46,13 +46,18 @@ test01()
   string four(to_string(ull2));
   VERIFY( four == "3000" );
 
+  string tail;
+#if __cpp_lib_to_string < 202306L
+  tail = ".000000";
+#endif
+
   long double ld1 = 2.0L;
   string five(to_string(ld1));
-  VERIFY( five == "2.000000" );
+  VERIFY( five == "2" + tail );
 
   long double ld2 = -4.0L;
   string six(to_string(ld2));
-  VERIFY( six == "-4.000000" );
+  VERIFY( six == "-4" + tail );
 }
 
 int main()
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string_float.cc b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string_float.cc
new file mode 100644
index 00000000000..3837c896f6b
--- /dev/null
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/char/to_string_float.cc
@@ -0,0 +1,148 @@
+// { dg-do run { target c++11 } }
+// { dg-require-namedlocale "de_DE.ISO8859-15" }
+
+// C++11 21.5 Numeric Conversions [string.conversions]
+
+#include <string>
+#include <format>
+#include <limits>
+#include <locale>
+#include <cstdio>
+#include <testsuite_hooks.h>
+
+namespace test
+{
+// Canonical version of std::to_string(double) as specified in the standard.
+
+#if __cplusplus > 202302L
+
+#ifndef __cpp_lib_to_string
+# error "Feature-test macro for std::to_string missing in <string>"
+#elif __cpp_lib_to_string != 202306L
+# error "Feature-test macro for std::to_string has wrong value in <string>"
+#endif
+
+static std::string to_string(float val) { return std::format("{}", val); }
+static std::string to_string(double val) { return std::format("{}", val); }
+static std::string to_string(long double val) { return std::format("{}", val); }
+
+#else
+
+#ifdef __cpp_lib_to_string
+# error "__cpp_lib_to_string should not be defined for C++23"
+#endif
+
+static std::string to_string(double val)
+{
+  std::string str(100, '9');
+retry:
+  const int size = str.size();
+  const int len = std::snprintf(&str[0], size + 1, "%f", val);
+  str.resize(len);
+  if (len > size)
+    goto retry;
+  return str;
+}
+
+// snprintf promotes float to double
+static std::string to_string(float val) { return to_string((double)val); }
+
+static std::string to_string(long double val)
+{
+  std::string str(100, '9');
+retry:
+  const int size = str.size();
+  const int len = std::snprintf(&str[0], size + 1, "%Lf", val);
+  str.resize(len);
+  if (len > size)
+    goto retry;
+  return str;
+}
+#endif
+} // namespace test
+
+template<typename T>
+  void check_value(T val)
+  {
+    const std::string s = std::to_string(val);
+    const std::string expected = test::to_string(val);
+    VERIFY( s == expected );
+    VERIFY( s[s.size()] == '\0' ); // null-terminator not overwritten
+  }
+
+template<typename T>
+  void check_values()
+  {
+    const T values[] = {
+      0.0, 0.0625, 0.25, 0.5, 1.25, 1e2, 1e7, 1e8, 1e-2, 1e-7, 1e-8,
+      2e38, 4.4e+19, 6.25e-12, 7.89e+23,
+      12345.6789, (T) 1234567890123456.e100L, (T) 1213141516e-99L,
+      std::numeric_limits<T>::min(),
+      std::numeric_limits<T>::max(),
+      std::numeric_limits<T>::epsilon(),
+      std::numeric_limits<T>::infinity(),
+      std::numeric_limits<T>::quiet_NaN(),
+    };
+
+    std::locale::global(std::locale::classic());
+
+    for (auto v : values)
+    {
+      check_value(v);
+      check_value(-v);
+    }
+
+    std::locale::global(std::locale(ISO_8859(15,de_DE)));
+
+    for (auto v : values)
+    {
+      check_value(v);
+      check_value(-v);
+    }
+
+    std::locale::global(std::locale::classic());
+  }
+
+void test01()
+{
+  // Examples from P2587R3 `to_string` or not `to_string`
+
+
+  VERIFY( std::to_string(42) == "42" );
+  VERIFY( std::to_string(12345) == "12345" );
+  auto max = std::to_string(1.7976931348623157e+308);
+
+#if __cplusplus <= 202302L
+  VERIFY( std::to_string(0.42) == "0.420000" );
+  VERIFY( std::to_string(1e-7) == "0.000000" );
+  VERIFY( std::to_string(-1e-7) == "-0.000000" );
+  VERIFY( max.substr(0, 17) == "17976931348623157" );
+  VERIFY( max.substr(max.size() - 7) == ".000000" );
+#else
+  VERIFY( std::to_string(0.42) == "0.42" );
+  VERIFY( std::to_string(1e-7) == "1e-07" );
+  VERIFY( std::to_string(-1e-7) == "-1e-07" );
+  VERIFY( max == "1.7976931348623157e+308" );
+#endif
+
+  std::locale::global(std::locale(ISO_8859(15,de_DE)));
+#if __cplusplus <= 202302L
+  VERIFY( std::to_string(1234.5) == "1234,500000" );
+#else
+  VERIFY( std::to_string(1234.5) == "1234.5" );
+#endif
+  std::locale::global(std::locale::classic());
+}
+
+void test02()
+{
+  check_values<float>();
+  check_values<double>();
+  check_values<long double>();
+}
+
+int main()
+{
+  test01();
+  test02();
+}
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/version.cc b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/version.cc
new file mode 100644
index 00000000000..630e06fff8f
--- /dev/null
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/version.cc
@@ -0,0 +1,18 @@
+// { dg-do compile }
+#include <version>
+
+#if __cplusplus > 202302L
+
+#ifndef __cpp_lib_to_string
+# error "Feature-test macro for std::to_string missing in <string>"
+#elif __cpp_lib_to_string != 202306L
+# error "Feature-test macro for std::to_string has wrong value in <string>"
+#endif
+
+#else
+
+#ifdef __cpp_lib_to_string
+# error "__cpp_lib_to_string should not be defined for C++23"
+#endif
+
+#endif
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/dr1261.cc b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/dr1261.cc
index 8dbdd7bf29b..c2b36fd6c24 100644
--- a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/dr1261.cc
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/dr1261.cc
@@ -46,14 +46,19 @@ void test01()
   const wstring six(to_wstring(400ull));
   VERIFY( six == L"400" );
 
+  wstring tail;
+#if __cpp_lib_to_string < 202306L
+  tail = L".000000";
+#endif
+
   const wstring seven(to_wstring(-1.0F));
-  VERIFY( seven == L"-1.000000" );
+  VERIFY( seven == L"-1" + tail );
 
   const wstring eight(to_wstring(2.0));
-  VERIFY( eight == L"2.000000" );
+  VERIFY( eight == L"2" + tail );
 
   const wstring nine(to_wstring(-4.0L));
-  VERIFY( nine == L"-4.000000" );
+  VERIFY( nine == L"-4" + tail );
 }
 
 int main()
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring.cc b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring.cc
index dbfb639765e..66987835bbb 100644
--- a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring.cc
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring.cc
@@ -47,13 +47,18 @@ test01()
   wstring four(to_wstring(ull2));
   VERIFY( four == L"3000" );
 
+  wstring tail;
+#if __cpp_lib_to_string < 202306L
+  tail = L".000000";
+#endif
+
   long double ld1 = 2.0L;
   wstring five(to_wstring(ld1));
-  VERIFY( five == L"2.000000" );
+  VERIFY( five == L"2" + tail );
 
   long double ld2 = -4.0L;
   wstring six(to_wstring(ld2));
-  VERIFY( six == L"-4.000000" );
+  VERIFY( six == L"-4" + tail );
 
 #endif
 }
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring_float.cc b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring_float.cc
new file mode 100644
index 00000000000..83f5bb1cb19
--- /dev/null
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/numeric_conversions/wchar_t/to_wstring_float.cc
@@ -0,0 +1,145 @@
+// { dg-do run { target c++11 } }
+// { dg-require-namedlocale "de_DE.ISO8859-15" }
+// { dg-require-string-conversions "" }
+
+// C++11 21.5 Numeric Conversions [string.conversions]
+
+#include <string>
+#include <format>
+#include <limits>
+#include <locale>
+#include <cstdio>
+#include <testsuite_hooks.h>
+
+namespace test
+{
+// Canonical version of std::to_wstring(double) as specified in the standard.
+
+#if __cplusplus > 202302L
+
+std::wstring to_wstring(float val) { return std::format(L"{}", val); }
+std::wstring to_wstring(double val) { return std::format(L"{}", val); }
+std::wstring to_wstring(long double val) { return std::format(L"{}", val); }
+
+#else
+
+std::wstring to_wstring(double val)
+{
+  std::wstring str(100, L'9');
+retry:
+  const int size = str.size();
+  const int len = std::swprintf(&str[0], size + 1, L"%f", val);
+  if (len == -1) // N.B. swprintf just returns -1 if the buffer is too small.
+  {
+    str.resize(size * 2);
+    goto retry;
+  }
+  str.resize(len);
+  return str;
+}
+
+// snprintf promotes float to double
+std::wstring to_wstring(float val) { return to_wstring((double)val); }
+
+std::wstring to_wstring(long double val)
+{
+  std::wstring str(100, L'9');
+retry:
+  const int size = str.size();
+  const int len = std::swprintf(&str[0], size + 1, L"%Lf", val);
+  if (len == -1) // N.B. swprintf just returns -1 if the buffer is too small.
+  {
+    str.resize(size * 2);
+    goto retry;
+  }
+  str.resize(len);
+  return str;
+}
+#endif
+} // namespace test
+
+template<typename T>
+  void check_value(T val)
+  {
+    const std::wstring s = std::to_wstring(val);
+    const std::wstring expected = test::to_wstring(val);
+    VERIFY( s == expected );
+    VERIFY( s[s.size()] == L'\0' ); // null-terminator not overwritten
+  }
+
+template<typename T>
+  void check_values()
+  {
+    const T values[] = {
+      0.0, 0.0625, 0.25, 0.5, 1.25, 1e2, 1e7, 1e8, 1e-2, 1e-7, 1e-8,
+      2e38, 4.4e+19, 6.25e-12, 7.89e+23,
+      12345.6789, (T) 1234567890123456.e100L, (T) 1213141516e-99L,
+      std::numeric_limits<T>::min(),
+      std::numeric_limits<T>::max(),
+      std::numeric_limits<T>::epsilon(),
+      std::numeric_limits<T>::infinity(),
+      std::numeric_limits<T>::quiet_NaN(),
+    };
+
+    std::locale::global(std::locale::classic());
+
+    for (auto v : values)
+    {
+      check_value(v);
+      check_value(-v);
+    }
+
+    std::locale::global(std::locale(ISO_8859(15,de_DE)));
+
+    for (auto v : values)
+    {
+      check_value(v);
+      check_value(-v);
+    }
+
+    std::locale::global(std::locale::classic());
+  }
+
+void test01()
+{
+  // Examples from P2587R3 `to_string` or not `to_string`
+
+
+  VERIFY( std::to_wstring(42) == L"42" );
+  VERIFY( std::to_wstring(12345) == L"12345" );
+  auto max = std::to_wstring(1.7976931348623157e+308);
+
+#if __cplusplus <= 202302L
+  VERIFY( std::to_wstring(0.42) == L"0.420000" );
+  VERIFY( std::to_wstring(1e-7) == L"0.000000" );
+  VERIFY( std::to_wstring(-1e-7) == L"-0.000000" );
+  VERIFY( max.substr(0, 17) == L"17976931348623157" );
+  VERIFY( max.substr(max.size() - 7) == L".000000" );
+#else
+  VERIFY( std::to_wstring(0.42) == L"0.42" );
+  VERIFY( std::to_wstring(1e-7) == L"1e-07" );
+  VERIFY( std::to_wstring(-1e-7) == L"-1e-07" );
+  VERIFY( max == L"1.7976931348623157e+308" );
+#endif
+
+  std::locale::global(std::locale(ISO_8859(15,de_DE)));
+#if __cplusplus <= 202302L
+  VERIFY( std::to_wstring(1234.5) == L"1234,500000" );
+#else
+  VERIFY( std::to_wstring(1234.5) == L"1234.5" );
+#endif
+  std::locale::global(std::locale::classic());
+}
+
+void test02()
+{
+  check_values<float>();
+  check_values<double>();
+  check_values<long double>();
+}
+
+int main()
+{
+  test01();
+  test02();
+}
-- 
2.41.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2023-08-17 20:31 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-08-17 20:31 [committed] libstdc++: Implement std::to_string in terms of std::format (P2587R3) Jonathan Wakely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).