From aeed687f4e08f1364ab60882a012fe740e05a7da Mon Sep 17 00:00:00 2001 From: Jonathan Wakely <jwakely@redhat.com> Date: Wed, 16 Aug 2023 16:55:00 +0100 Subject: [PATCH] libstdc++: Implement std::to_string in terms of std::format (P2587R3) 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 46326d025974..f4bbf521bba7 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 e20dd3e3c0b2..b50050440d95 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 7a276de70efb..8b8c70a6e532 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 49998b54f634..b952c26c328a 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 dc7b87b85f55..c770b4f9bdbf 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 000000000000..3837c896f6b0 --- /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 000000000000..630e06fff8ff --- /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 8dbdd7bf29bb..c2b36fd6c247 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 dbfb639765ec..66987835bbb1 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 000000000000..83f5bb1cb193 --- /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(); +} -- GitLab