libstdc++: Add std::from_chars for floating-point types
authorJonathan Wakely <jwakely@redhat.com>
Mon, 20 Jul 2020 22:49:27 +0000 (23:49 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Mon, 20 Jul 2020 22:49:27 +0000 (23:49 +0100)
This adds the missing std::from_chars overloads for floating-point
types, as required for C++17 conformance.

The implementation is a hack and not intended to be used in the long
term. Rather than parsing the string directly, this determines the
initial portion of the string that matches the pattern determined by the
chars_format parameter, then creates a NTBS to be parsed by strtod (or
strtold or strtof).

Because creating a NTBS requires allocating memory, but std::from_chars
is noexcept, we need to be careful to minimise allocation. Even after
being careful, allocation failure is still possible, and so a
non-conforming std::no_more_memory error code might be returned.

Because strtod et al depend on the current locale, but std::from_chars
does not, we change the current thread's locale to "C" using newlocale
and uselocale before calling strtod, and restore it afterwards.

Because strtod doesn't have the equivalent of a std::chars_format
parameter, it has to examine the input to determine the format in use,
even though the std::from_chars code has already parsed it once (or
twice for large input strings!)

By replacing the use of strtod we could avoid allocation, avoid changing
locale, and use optimised code paths specific to each std::chars_format
case. We would also get more portable behaviour, rather than depending
on the presence of uselocale, and on any bugs or quirks of the target
libc's strtod. Replacing strtod is a project for a later date.

libstdc++-v3/ChangeLog:

* acinclude.m4 (libtool_VERSION): Bump version.
* config.h.in: Regenerate.
* config/abi/pre/gnu.ver: Add GLIBCXX_3.4.29 version and new
exports.
* config/os/gnu-linux/ldbl-extra.ver: Add _GLIBCXX_LDBL_3.4.29
version and new export.
* configure: Regenerate.
* configure.ac: Check for <xlocale.h> and uselocale.
* crossconfig.m4: Add macro or checks for uselocale.
* include/std/charconv (from_chars): Declare overloads for
float, double, and long double.
* src/c++17/Makefile.am: Add new file.
* src/c++17/Makefile.in: Regenerate.
* src/c++17/floating_from_chars.cc: New file.
(from_chars): Define for float, double, and long double.
* testsuite/20_util/from_chars/1_c++20_neg.cc: Prune extra
diagnostics caused by new overloads.
* testsuite/20_util/from_chars/1_neg.cc: Likewise.
* testsuite/20_util/from_chars/2.cc: Check leading '+'.
* testsuite/20_util/from_chars/4.cc: New test.
* testsuite/20_util/from_chars/5.cc: New test.
* testsuite/util/testsuite_abi.cc: Add new symbol versions.

17 files changed:
libstdc++-v3/acinclude.m4
libstdc++-v3/config.h.in
libstdc++-v3/config/abi/pre/gnu.ver
libstdc++-v3/config/os/gnu-linux/ldbl-extra.ver
libstdc++-v3/configure
libstdc++-v3/configure.ac
libstdc++-v3/crossconfig.m4
libstdc++-v3/include/std/charconv
libstdc++-v3/src/c++17/Makefile.am
libstdc++-v3/src/c++17/Makefile.in
libstdc++-v3/src/c++17/floating_from_chars.cc [new file with mode: 0644]
libstdc++-v3/testsuite/20_util/from_chars/1_c++20_neg.cc
libstdc++-v3/testsuite/20_util/from_chars/1_neg.cc
libstdc++-v3/testsuite/20_util/from_chars/2.cc
libstdc++-v3/testsuite/20_util/from_chars/4.cc [new file with mode: 0644]
libstdc++-v3/testsuite/20_util/from_chars/5.cc [new file with mode: 0644]
libstdc++-v3/testsuite/util/testsuite_abi.cc

index ee5e0336f2c3e0c32a0ff9c72528869bb2fcb22a..e3926e1c9c270efab732ea5dec549877b316d600 100644 (file)
@@ -3846,7 +3846,7 @@ changequote([,])dnl
 fi
 
 # For libtool versioning info, format is CURRENT:REVISION:AGE
-libtool_VERSION=6:28:0
+libtool_VERSION=6:29:0
 
 # Everything parsed; figure out what files and settings to use.
 case $enable_symvers in
index 8940e0c7acd1b9aea7bf1ea40727899c69d322fb..8ae3e0fc4bf89143bedcd538ac973104019e540c 100644 (file)
 /* Define to 1 if you have the <unistd.h> header file. */
 #undef HAVE_UNISTD_H
 
+/* Define to 1 if you have the `uselocale' function. */
+#undef HAVE_USELOCALE
+
 /* Defined if usleep exists. */
 #undef HAVE_USLEEP
 
 /* Define if writev is available in <sys/uio.h>. */
 #undef HAVE_WRITEV
 
+/* Define to 1 if you have the <xlocale.h> header file. */
+#undef HAVE_XLOCALE_H
+
 /* Define to 1 if you have the `_acosf' function. */
 #undef HAVE__ACOSF
 
index edf4485e6079558d39fc3d48119405bb66f4bb0a..17aff5d907b2d1dd41b36602ba6e70472310ac70 100644 (file)
@@ -2299,6 +2299,13 @@ GLIBCXX_3.4.28 {
 
 } GLIBCXX_3.4.27;
 
+GLIBCXX_3.4.29 {
+
+    # std::from_chars
+    _ZSt10from_charsPKcS0_R[def]St12chars_format;
+
+} GLIBCXX_3.4.28;
+
 # Symbols in the support library (libsupc++) have their own tag.
 CXXABI_1.3 {
 
index 5ef4a6cb6e1e6d517b4b44c26c8c6cb0e32c7119..b4f3af0f9d9ba7869144830a0e46659c34f9c989 100644 (file)
@@ -40,6 +40,10 @@ GLIBCXX_LDBL_3.4.21 {
   __gnu_cxx_ldbl1287num_getI[cw]*16_M_extract_floatB5cxx11*;
 } GLIBCXX_LDBL_3.4.10;
 
+GLIBCXX_LDBL_3.4.29 {
+  _ZSt10from_charsPKcS0_RgSt12chars_format;
+} GLIBCXX_LDBL_3.4.21;
+
 CXXABI_LDBL_1.3 {
   _ZT[IS]g;
   _ZT[IS]Pg;
index dd54bd406a9a341c1c69bc561652904b33021b39..8d16bf3ffd6b17ef61e1f13d038f3292b36d1189 100755 (executable)
 done
 
 
+for ac_header in xlocale.h
+do :
+  ac_fn_c_check_header_mongrel "$LINENO" "xlocale.h" "ac_cv_header_xlocale_h" "$ac_includes_default"
+if test "x$ac_cv_header_xlocale_h" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_XLOCALE_H 1
+_ACEOF
+
+fi
+
+done
+
+
 # Only do link tests if native. Else, hardcode.
 if $GLIBCXX_IS_NATIVE; then
 
@@ -28986,6 +28999,19 @@ if test "x$ac_cv_func_sockatmark" = xyes; then :
 #define HAVE_SOCKATMARK 1
 _ACEOF
 
+fi
+done
+
+
+  # Non-standard functions used by C++17 std::from_chars
+  for ac_func in uselocale
+do :
+  ac_fn_c_check_func "$LINENO" "uselocale" "ac_cv_func_uselocale"
+if test "x$ac_cv_func_uselocale" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_USELOCALE 1
+_ACEOF
+
 fi
 done
 
@@ -41997,6 +42023,9 @@ _ACEOF
 
   fi
 
+
+    $as_echo "#define HAVE_USELOCALE 1" >>confdefs.h
+
     ;;
 
   *-darwin*)
@@ -47770,6 +47799,18 @@ done
 
   CXXFLAGS="$ac_save_CXXFLAGS"
 
+
+    for ac_func in uselocale
+do :
+  ac_fn_c_check_func "$LINENO" "uselocale" "ac_cv_func_uselocale"
+if test "x$ac_cv_func_uselocale" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_USELOCALE 1
+_ACEOF
+
+fi
+done
+
     ;;
 
   *djgpp)
@@ -48041,6 +48082,17 @@ if test "x$ac_cv_func_sockatmark" = xyes; then :
 #define HAVE_SOCKATMARK 1
 _ACEOF
 
+fi
+done
+
+    for ac_func in uselocale
+do :
+  ac_fn_c_check_func "$LINENO" "uselocale" "ac_cv_func_uselocale"
+if test "x$ac_cv_func_uselocale" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_USELOCALE 1
+_ACEOF
+
 fi
 done
 
@@ -54682,6 +54734,17 @@ _ACEOF
 fi
 done
 
+    for ac_func in uselocale
+do :
+  ac_fn_c_check_func "$LINENO" "uselocale" "ac_cv_func_uselocale"
+if test "x$ac_cv_func_uselocale" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_USELOCALE 1
+_ACEOF
+
+fi
+done
+
 
 
 
@@ -75231,7 +75294,7 @@ $as_echo "$as_me: WARNING: === Symbol versioning will be disabled." >&2;}
 fi
 
 # For libtool versioning info, format is CURRENT:REVISION:AGE
-libtool_VERSION=6:28:0
+libtool_VERSION=6:29:0
 
 # Everything parsed; figure out what files and settings to use.
 case $enable_symvers in
index ffd0079613fee466f4fb1b0a6ab5e6c429cd7ca1..cbfdf4c6bad12a4eaaebdf3a70e6e174cc71a9bf 100644 (file)
@@ -256,6 +256,8 @@ AC_CHECK_HEADERS([linux/random.h], [], [],
 #endif
 ]])
 
+AC_CHECK_HEADERS([xlocale.h])
+
 # Only do link tests if native. Else, hardcode.
 if $GLIBCXX_IS_NATIVE; then
 
@@ -282,6 +284,9 @@ if $GLIBCXX_IS_NATIVE; then
   # For Networking TS.
   AC_CHECK_FUNCS(sockatmark)
 
+  # Non-standard functions used by C++17 std::from_chars
+  AC_CHECK_FUNCS(uselocale)
+
   # For iconv support.
   AM_ICONV
 
index 313f84d05f49a95a4def2adfd2804b75ac52f724..9f2589b739ebffc9835de1f6b67ccf6a9570b47c 100644 (file)
@@ -63,6 +63,8 @@ case "${host}" in
     # We don't yet support AIX's TLS ABI.
     #GCC_CHECK_TLS
     AM_ICONV
+
+    AC_DEFINE(HAVE_USELOCALE)
     ;;
 
   *-darwin*)
@@ -73,6 +75,8 @@ case "${host}" in
     # Don't call GLIBCXX_CHECK_LINKER_FEATURES, Darwin doesn't have a GNU ld
     GLIBCXX_CHECK_MATH_SUPPORT
     GLIBCXX_CHECK_STDLIB_SUPPORT
+
+    AC_CHECK_FUNCS(uselocale)
     ;;
 
   *djgpp)
@@ -129,6 +133,7 @@ case "${host}" in
     AC_CHECK_FUNCS(aligned_alloc posix_memalign memalign _aligned_malloc)
     AC_CHECK_FUNCS(timespec_get)
     AC_CHECK_FUNCS(sockatmark)
+    AC_CHECK_FUNCS(uselocale)
     ;;
 
   *-fuchsia*)
@@ -190,6 +195,7 @@ case "${host}" in
     AC_CHECK_FUNCS(aligned_alloc posix_memalign memalign _aligned_malloc)
     AC_CHECK_FUNCS(timespec_get)
     AC_CHECK_FUNCS(sockatmark)
+    AC_CHECK_FUNCS(uselocale)
     AM_ICONV
     ;;
   *-mingw32*)
index cc7dd0e3758f32f77d083bb23b98a0a2c13646c8..be668c1939e3d54d80e352ede69e99379ce7b40a 100644 (file)
@@ -688,6 +688,20 @@ namespace __detail
   operator^=(chars_format& __lhs, chars_format __rhs) noexcept
   { return __lhs = __lhs ^ __rhs; }
 
+#if _GLIBCXX_HAVE_USELOCALE
+  from_chars_result
+  from_chars(const char* __first, const char* __last, float& __value,
+            chars_format __fmt = chars_format::general);
+
+  from_chars_result
+  from_chars(const char* __first, const char* __last, double& __value,
+            chars_format __fmt = chars_format::general);
+
+  from_chars_result
+  from_chars(const char* __first, const char* __last, long double& __value,
+            chars_format __fmt = chars_format::general);
+#endif
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace std
 #endif // C++14
index 85e31fbd91ffd0dbade7c3fdd80b7112c925f8fd..642efb976acc1ac7f2b908f93a205320077f715b 100644 (file)
@@ -50,6 +50,7 @@ inst_sources =
 endif
 
 sources = \
+       floating_from_chars.cc \
        fs_dir.cc \
        fs_ops.cc \
        fs_path.cc \
index de605a3f6a6a9a6dd4c4ca94183553704e7d50d2..ce08eb3ff111f9753b5a9a8001a6c0402d59f380 100644 (file)
@@ -124,8 +124,8 @@ LTLIBRARIES = $(noinst_LTLIBRARIES)
 libc__17convenience_la_LIBADD =
 @ENABLE_DUAL_ABI_TRUE@am__objects_1 = cow-fs_dir.lo cow-fs_ops.lo \
 @ENABLE_DUAL_ABI_TRUE@ cow-fs_path.lo
-am__objects_2 = fs_dir.lo fs_ops.lo fs_path.lo memory_resource.lo \
-       $(am__objects_1)
+am__objects_2 = floating_from_chars.lo fs_dir.lo fs_ops.lo fs_path.lo \
+       memory_resource.lo $(am__objects_1)
 @ENABLE_DUAL_ABI_TRUE@am__objects_3 = cow-string-inst.lo
 @ENABLE_EXTERN_TEMPLATE_TRUE@am__objects_4 = ostream-inst.lo \
 @ENABLE_EXTERN_TEMPLATE_TRUE@  string-inst.lo $(am__objects_3)
@@ -435,6 +435,7 @@ headers =
 @ENABLE_EXTERN_TEMPLATE_TRUE@  $(extra_string_inst_sources)
 
 sources = \
+       floating_from_chars.cc \
        fs_dir.cc \
        fs_ops.cc \
        fs_path.cc \
diff --git a/libstdc++-v3/src/c++17/floating_from_chars.cc b/libstdc++-v3/src/c++17/floating_from_chars.cc
new file mode 100644 (file)
index 0000000..45de2be
--- /dev/null
@@ -0,0 +1,422 @@
+// std::from_chars implementation for floating-point types -*- C++ -*-
+
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+//
+// ISO C++ 14882:2017
+// 23.2.9  Primitive numeric input conversion [utility.from.chars]
+//
+
+#include <charconv>
+#include <string>
+#include <memory_resource>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <cctype>
+#include <locale.h>
+#include <bits/functexcept.h>
+#if _GLIBCXX_HAVE_XLOCALE_H
+# include <xlocale.h>
+#endif
+
+#if _GLIBCXX_HAVE_USELOCALE
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+_GLIBCXX_BEGIN_NAMESPACE_VERSION
+
+namespace
+{
+  // A memory resource with a static buffer that can be used for small
+  // allocations. At most one allocation using the freestore can be done
+  // if the static buffer is insufficient. The callers below only require
+  // a single allocation, so there's no need for anything more complex.
+  struct buffer_resource : pmr::memory_resource
+  {
+    ~buffer_resource() { if (m_ptr) operator delete(m_ptr, m_bytes); }
+
+    void*
+    do_allocate(size_t bytes, size_t alignment [[maybe_unused]]) override
+    {
+      // Allocate from the buffer if it will fit.
+      if (m_bytes < sizeof(m_buf) && (m_bytes + bytes) <= sizeof(m_buf))
+       return m_buf + std::__exchange(m_bytes, m_bytes + bytes);
+
+      __glibcxx_assert(m_ptr == nullptr);
+      __glibcxx_assert(alignment != 1);
+
+      m_ptr = operator new(bytes);
+      m_bytes = bytes;
+      return m_ptr;
+    }
+
+    void
+    do_deallocate(void*, size_t, size_t) noexcept override
+    { /* like pmr::monotonic_buffer_resource, do nothing here */ }
+
+    bool
+    do_is_equal(const pmr::memory_resource& other) const noexcept override
+    { return &other == this; }
+
+    static constexpr int guaranteed_capacity() { return sizeof(m_buf); }
+
+  private:
+    char m_buf[512];
+    size_t m_bytes = 0;
+    void* m_ptr = nullptr;
+  };
+
+  inline bool valid_fmt(chars_format fmt)
+  {
+    return fmt != chars_format{}
+      && ((fmt & chars_format::general) == fmt
+         || (fmt & chars_format::hex) == fmt);
+  }
+
+  constexpr char hex_digits[] = "abcdefABCDEF0123456789";
+  constexpr auto dec_digits = hex_digits + 12;
+
+  // Find initial portion of [first, last) containing a floating-point number.
+  // The string `digits` is either `dec_digits` or `hex_digits`
+  // and `exp` is 'e' or 'p' or '\0'.
+  const char*
+  find_end_of_float(const char* first, const char* last, const char* digits,
+                   char exp)
+  {
+    while (first < last && strchr(digits, *first) != nullptr)
+      ++first;
+    if (first < last && *first == '.')
+      {
+       ++first;
+       while (first < last && strchr(digits, *first))
+         ++first;
+      }
+    if (first < last && exp != 0 && std::tolower((unsigned char)*first) == exp)
+      {
+       ++first;
+       if (first < last && (*first == '-' || *first == '+'))
+         ++first;
+       while (first < last && strchr(dec_digits, *first) != nullptr)
+         ++first;
+      }
+    return first;
+  }
+
+  // Determine the prefix of [first, last) that matches the pattern
+  // corresponding to `fmt`.
+  // Returns a NTBS containing the pattern, using `buf` to allocate
+  // additional storage if needed.
+  // Returns a nullptr if a valid pattern is not present.
+  const char*
+  pattern(const char* const first, const char* last,
+         chars_format& fmt, pmr::string& buf)
+  {
+    // fmt has the value of one of the enumerators of chars_format.
+    __glibcxx_assert(valid_fmt(fmt));
+
+    string_view res;
+
+    if (first == last || *first == '+') [[unlikely]]
+       return nullptr;
+
+    const int neg = (*first == '-');
+
+    if (std::memchr("iInN", (unsigned char)first[neg], 4))
+      {
+       ptrdiff_t len = last - first;
+       if (len < (3 + neg))
+         return nullptr;
+
+       // possible infinity or NaN, let strtod decide
+       if (first[neg] == 'i' || first[neg] == 'I')
+         {
+           // Need at most 9 chars for "-INFINITY", ignore anything after it.
+           len = std::min(len, ptrdiff_t(neg + 8));
+         }
+       else if (len > (neg + 3) && first[neg + 3] == '(')
+         {
+           // Look for end of "NAN(n-char-sequence)"
+           if (void* p = std::memchr(const_cast<char*>(first)+4, ')', len-4))
+             len = static_cast<char*>(p) + 1 - first;
+#ifndef __cpp_exceptions
+           if (len > buffer_resource::guaranteed_capacity())
+             {
+               // The character sequence is too large for the buffer.
+               // Allocation failure could terminate the process,
+               // so just return an error via the fmt parameter.
+               fmt = chars_format{};
+               return nullptr;
+             }
+#endif
+         }
+       else // Only need 4 chars for "-NAN"
+         len = neg + 3;
+
+       buf.assign(first, 0, len);
+       // prevent make_result correcting for "0x"
+       fmt = chars_format::general;
+       return buf.c_str();
+      }
+
+    const char* digits;
+    char* ptr;
+
+    // Assign [first,last) to a std::string to get a NTBS that can be used
+    // with strspn, strtod etc.
+    // If the string would be longer than the fixed buffer inside the
+    // buffer_resource type use find_end_of_float to try to reduce how
+    // much memory is needed, to reduce the chance of std::bad_alloc.
+
+    if (fmt == chars_format::hex)
+      {
+       digits = hex_digits;
+
+       if ((last - first + 2) > buffer_resource::guaranteed_capacity())
+         {
+           last = find_end_of_float(first + neg, last, digits, 'p');
+#ifndef __cpp_exceptions
+           if ((last - first + 2) > buffer_resource::guaranteed_capacity())
+             {
+               // The character sequence is still too large for the buffer.
+               // Allocation failure could terminate the process,
+               // so just return an error via the fmt parameter.
+               fmt = chars_format{};
+               return nullptr;
+             }
+#endif
+         }
+
+       buf = "-0x" + !neg;
+       buf.append(first + neg, last);
+       ptr = buf.data() + neg + 2;
+      }
+    else
+      {
+       digits = dec_digits;
+
+       if ((last - first) > buffer_resource::guaranteed_capacity())
+         {
+           last = find_end_of_float(first + neg, last, digits,
+                                    "e"[fmt == chars_format::fixed]);
+#ifndef __cpp_exceptions
+           if ((last - first) > buffer_resource::guaranteed_capacity())
+             {
+               // The character sequence is still too large for the buffer.
+               // Allocation failure could terminate the process,
+               // so just return an error via the fmt parameter.
+               fmt = chars_format{};
+               return nullptr;
+             }
+#endif
+         }
+       buf.assign(first, last);
+       ptr = buf.data() + neg;
+      }
+
+    // "A non-empty sequence of decimal digits" or
+    // "A non-empty sequence of hexadecimal digits"
+    size_t len = std::strspn(ptr, digits);
+    // "possibly containing a radix character,"
+    if (ptr[len] == '.')
+      {
+       const size_t len2 = std::strspn(ptr + len + 1, digits);
+       if (len + len2)
+         ptr += len + 1 + len2;
+       else
+         return nullptr;
+      }
+    else if (len == 0) [[unlikely]]
+      return nullptr;
+    else
+      ptr += len;
+
+    if (fmt == chars_format::fixed)
+      {
+       // Truncate the string to stop strtod parsing past this point.
+       *ptr = '\0';
+      }
+    else if (fmt == chars_format::scientific)
+      {
+       // Check for required exponent part which starts with 'e' or 'E'
+       if (*ptr != 'e' && *ptr != 'E')
+         return nullptr;
+       // then an optional plus or minus sign
+       const int sign = (ptr[1] == '-' || ptr[1] == '+');
+       // then a nonempty sequence of decimal digits
+       if (!std::memchr(dec_digits, (unsigned char)ptr[1+sign], 10))
+         return nullptr;
+      }
+    else if (fmt == chars_format::general)
+      {
+       if (*ptr == 'x' || *ptr == 'X')
+         *ptr = '\0';
+      }
+
+    return buf.c_str();
+  }
+
+  // Convert the NTBS `str` to a floating-point value of type `T`.
+  // If `str` cannot be converted, `value` is unchanged and `0` is returned.
+  // Otherwise, let N be the number of characters consumed from `str`.
+  // On success `value` is set to the converted value and N is returned.
+  // If the converted value is out of range, `value` is unchanged and
+  // -N is returned.
+  template<typename T>
+  ptrdiff_t
+  from_chars_impl(const char* str, T& value, errc& ec) noexcept
+  {
+    if (locale_t loc = ::newlocale(LC_ALL, "C", (locale_t)0)) [[likely]]
+      {
+       locale_t orig = ::uselocale(loc);
+
+       const int save_errno = errno;
+       errno = 0;
+       char* endptr;
+       T tmpval;
+       if constexpr (is_same_v<T, float>)
+         tmpval = std::strtof(str, &endptr);
+       if constexpr (is_same_v<T, double>)
+         tmpval = std::strtod(str, &endptr);
+       else if constexpr (is_same_v<T, long double>)
+         tmpval = std::strtold(str, &endptr);
+       const int conv_errno = std::__exchange(errno, save_errno);
+
+       ::uselocale(orig);
+       ::freelocale(loc);
+
+       const ptrdiff_t n = endptr - str;
+       if (conv_errno == ERANGE) [[unlikely]]
+         {
+           if (std::isinf(tmpval)) // overflow
+             ec = errc::result_out_of_range;
+           else // underflow (LWG 3081 wants to set value = tmpval here)
+             ec = errc::result_out_of_range;
+         }
+       else if (n)
+         {
+           value = tmpval;
+           ec = errc();
+         }
+       return n;
+      }
+    else if (errno == ENOMEM)
+      ec = errc::not_enough_memory;
+
+    return 0;
+  }
+
+  inline from_chars_result
+  make_result(const char* str, ptrdiff_t n, chars_format fmt, errc ec) noexcept
+  {
+    from_chars_result result = { str, ec };
+    if (n != 0)
+      {
+       if (fmt == chars_format::hex)
+         n -= 2; // correct for the "0x" inserted into the pattern
+       result.ptr += n;
+      }
+    else if (fmt == chars_format{}) [[unlikely]]
+      {
+       // FIXME: the standard does not allow this result.
+       ec = errc::not_enough_memory;
+      }
+    return result;
+  }
+
+} // namespace
+
+// FIXME: This should be reimplemented so it doesn't use strtod and newlocale.
+// That will avoid the need for any memory allocation, meaning that the
+// non-conforming errc::not_enough_memory result cannot happen.
+
+from_chars_result
+from_chars(const char* first, const char* last, float& value,
+          chars_format fmt) noexcept
+{
+  buffer_resource mr;
+  pmr::string buf(&mr);
+  size_t len = 0;
+  errc ec = errc::invalid_argument;
+  __try
+    {
+      if (const char* pat = pattern(first, last, fmt, buf)) [[likely]]
+       len = from_chars_impl(pat, value, ec);
+    }
+  __catch (const std::bad_alloc&)
+    {
+      fmt = chars_format{};
+    }
+  return make_result(first, len, fmt, ec);
+}
+
+from_chars_result
+from_chars(const char* first, const char* last, double& value,
+          chars_format fmt) noexcept
+{
+  buffer_resource mr;
+  pmr::string buf(&mr);
+  size_t len = 0;
+  errc ec = errc::invalid_argument;
+  __try
+    {
+      if (const char* pat = pattern(first, last, fmt, buf)) [[likely]]
+       len = from_chars_impl(pat, value, ec);
+    }
+  __catch (const std::bad_alloc&)
+    {
+      fmt = chars_format{};
+    }
+  return make_result(first, len, fmt, ec);
+}
+
+from_chars_result
+from_chars(const char* first, const char* last, long double& value,
+          chars_format fmt) noexcept
+{
+  buffer_resource mr;
+  pmr::string buf(&mr);
+  size_t len = 0;
+  errc ec = errc::invalid_argument;
+  __try
+    {
+      if (const char* pat = pattern(first, last, fmt, buf)) [[likely]]
+       len = from_chars_impl(pat, value, ec);
+    }
+  __catch (const std::bad_alloc&)
+    {
+      fmt = chars_format{};
+    }
+  return make_result(first, len, fmt, ec);
+}
+
+#ifdef _GLIBCXX_LONG_DOUBLE_COMPAT
+extern "C" from_chars_result
+_ZSt10from_charsPKcS0_ReSt12chars_format(const char* first, const char* last,
+                                        long double& value,
+                                        chars_format fmt) noexcept
+__attribute__((alias ("_ZSt10from_charsPKcS0_RdSt12chars_format")));
+#endif
+
+_GLIBCXX_END_NAMESPACE_VERSION
+} // namespace std
+#endif // _GLIBCXX_HAVE_USELOCALE
index 8454b304d1384349d547b3cef216151d118f8276..b3ca6525681282770d2e13b7c5570bdf41858f2e 100644 (file)
@@ -41,3 +41,4 @@ test01(const char* first, const char* last)
 }
 
 // { dg-prune-output "enable_if" }
+// { dg-prune-output "cannot bind non-const lvalue reference" }
index 12b5e597b9f5861514e5920091256da53d87072c..0d2fe2b3e659482e1dc491bfb4019c897b00aa3c 100644 (file)
@@ -44,3 +44,4 @@ test01(const char* first, const char* last)
 }
 
 // { dg-prune-output "enable_if" }
+// { dg-prune-output "cannot bind non-const lvalue reference" }
index 902092fd42312c719fb71055d5778d383bf5eb82..e5e2db82c3fd4abe6f30196b4e3eca4cf66215ad 100644 (file)
@@ -55,6 +55,12 @@ test01()
   VERIFY( r.ptr == s.data() );
   VERIFY( i == 999 );
 
+  s = "+1";
+  r = std::from_chars(s.data(), s.data() + s.length(), i);
+  VERIFY( r.ec == std::errc::invalid_argument );
+  VERIFY( r.ptr == s.data() );
+  VERIFY( i == 999 );
+
   unsigned u = 888;
   s = "-1";
   r = std::from_chars(s.data(), s.data() + s.length(), u);
@@ -69,6 +75,11 @@ test01()
   VERIFY( r.ec == std::errc::invalid_argument );
   VERIFY( r.ptr == s.data() );
   VERIFY( u == 888 );
+  s = "+1";
+  r = std::from_chars(s.data(), s.data() + s.length(), u);
+  VERIFY( r.ec == std::errc::invalid_argument );
+  VERIFY( r.ptr == s.data() );
+  VERIFY( u == 888 );
 
   for (int base = 2; base <= 36; ++base)
   {
diff --git a/libstdc++-v3/testsuite/20_util/from_chars/4.cc b/libstdc++-v3/testsuite/20_util/from_chars/4.cc
new file mode 100644 (file)
index 0000000..6d69259
--- /dev/null
@@ -0,0 +1,368 @@
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// <charconv> is supported in C++14 as a GNU extension
+// { dg-do run { target c++14 } }
+
+#include <charconv>
+#include <string>
+#include <limits>
+#include <cmath>
+#include <cstdlib>
+#include <testsuite_hooks.h>
+
+// Test std::from_chars floating-point conversions.
+
+void
+test01()
+{
+  std::string s;
+  double d;
+  std::from_chars_result res;
+
+  for (auto fmt : { std::chars_format::fixed, std::chars_format::scientific,
+                   std::chars_format::general, std::chars_format::hex })
+  {
+    s = "Info";
+    res = std::from_chars(s.data(), s.data() + s.length(), d, fmt);
+    VERIFY( std::isinf(d) );
+    VERIFY( res.ptr == s.data() + 3 );
+    VERIFY( res.ec == std::errc{} );
+
+    s = "-INFIN";
+    res = std::from_chars(s.data(), s.data() + s.length(), d, fmt);
+    VERIFY( std::isinf(d) );
+    VERIFY( d < 0 );
+    VERIFY( res.ptr == s.data() + 4 );
+    VERIFY( res.ec == std::errc{} );
+
+    s = "InFiNiTy aNd BeYoNd";
+    res = std::from_chars(s.data(), s.data() + s.length(), d, fmt);
+    VERIFY( std::isinf(d) );
+    VERIFY( res.ptr == s.data() + 8 );
+    VERIFY( res.ec == std::errc{} );
+
+    s = "nAn";
+    res = std::from_chars(s.data(), s.data() + s.length(), d, fmt);
+    VERIFY( std::isnan(d) );
+    VERIFY( res.ptr == s.data() + 3 );
+    VERIFY( res.ec == std::errc{} );
+
+    s = "-NAN()";
+    res = std::from_chars(s.data(), s.data() + s.length(), d, fmt);
+    VERIFY( std::isnan(d) );
+    VERIFY( res.ptr == s.data() + s.length() );
+    VERIFY( res.ec == std::errc{} );
+  }
+}
+
+void
+test02()
+{
+  std::string s;
+  double d = 1.0;
+  std::from_chars_result res;
+
+  s = "0x123";
+  res = std::from_chars(s.data(), s.data() + s.length(), d);
+  VERIFY( d == 0.0 );
+  VERIFY( res.ptr == s.data() + 1 );
+  VERIFY( res.ec == std::errc{} );
+
+  d = 1.0;
+  res = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::fixed);
+  VERIFY( d == 0.0 );
+  VERIFY( res.ptr == s.data() + 1 );
+  VERIFY( res.ec == std::errc{} );
+
+  d = 1.0;
+  res = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::scientific);
+  VERIFY( d == 1.0 );
+  VERIFY( res.ptr == s.data() );
+  VERIFY( res.ec == std::errc::invalid_argument );
+
+  d = 1.0;
+  res = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::general);
+  VERIFY( d == 0.0 );
+  VERIFY( res.ptr == s.data() + 1 );
+  VERIFY( res.ec == std::errc{} );
+
+  d = 1.0;
+  res = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::hex);
+  VERIFY( d == 0.0 );
+  VERIFY( res.ptr == s.data() + 1 );
+  VERIFY( res.ec == std::errc{} );
+}
+
+void
+test03()
+{
+  std::string s;
+  double d = 1.0;
+  std::from_chars_result res;
+
+  s = "0.5e+2azzz";
+  res = std::from_chars(s.data(), s.data() + s.length(), d);
+  VERIFY( d == 0.5e+2 );
+  VERIFY( res.ptr == s.data() + s.length() - 1 - 3 );
+  VERIFY( res.ec == std::errc{} );
+
+  res = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::fixed);
+  VERIFY( d == 0.5 );
+  VERIFY( res.ptr == s.data() + 3 );
+  VERIFY( res.ec == std::errc{} );
+
+  d = 1.0;
+  res = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::scientific);
+  VERIFY( d == 0.5e+2 );
+  VERIFY( res.ptr == s.data() + s.length() - 1 - 3 );
+  VERIFY( res.ec == std::errc{} );
+
+  d = 1.0;
+  res = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::general);
+  VERIFY( d == 0.5e+2 );
+  VERIFY( res.ptr == s.data() + s.length() - 1 - 3 );
+  VERIFY( res.ec == std::errc{} );
+
+  d = 1.0;
+  res = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::hex);
+  VERIFY( d == 0x0.5Ep0 );
+  VERIFY( res.ptr == s.data() + 4 );
+  VERIFY( res.ec == std::errc{} );
+
+  s = "1.Ap-2zzz";
+  res = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::hex);
+  VERIFY( d == 0.40625 );
+  VERIFY( res.ptr == s.data() + s.length() - 3 );
+  VERIFY( res.ec == std::errc{} );
+}
+
+void
+test04()
+{
+  // Huge input strings
+  std::string s(1000, '0');
+  double d = 1.0;
+  std::from_chars_result res;
+  res = std::from_chars(s.data(), s.data() + s.length(), d);
+  VERIFY( res.ptr == s.data() + s.length() );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( d == 0.0 );
+
+  s += ".5";
+  res = std::from_chars(s.data(), s.data() + s.length(), d);
+  VERIFY( res.ptr == s.data() + s.length() );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( d == 0.5 );
+
+  s += "e2";
+  auto len = s.length();
+  s += std::string(1000, 'a');
+  res = std::from_chars(s.data(), s.data() + s.length(), d);
+  VERIFY( res.ptr == s.data() + len );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( d == 50 );
+}
+
+using std::to_string;
+
+#ifdef __GLIBCXX_TYPE_INT_N_0
+std::string
+to_string(unsigned __GLIBCXX_TYPE_INT_N_0 val)
+{
+  using Limits = std::numeric_limits<unsigned __GLIBCXX_TYPE_INT_N_0>;
+  std::string s(Limits::digits10+2, '0');
+  for (auto iter = s.end(); val != 0; val /= 10)
+    *--iter = '0' + (val % 10);
+  return s;
+}
+#endif
+
+void
+test05()
+{
+  std::from_chars_result res;
+  float flt;
+  double dbl;
+  long double ldbl;
+
+  // Small integer values that are exactly representable
+
+  for (int i = 0; i < 100; ++i)
+  {
+    std::string s = to_string(i);
+    int len = s.length();
+    s += "123";
+    const char* s1 = s.c_str();
+    const char* s1_end = s1 + len;
+
+    for (auto fmt : { std::chars_format::fixed,
+                     std::chars_format::general,
+                     std::chars_format::hex })
+    {
+      if (fmt == std::chars_format::hex && i > 9)
+       continue;
+
+      res = std::from_chars(s1, s1_end, flt, fmt);
+      VERIFY( res.ec == std::errc{} );
+      VERIFY( res.ptr == s1_end );
+      VERIFY( flt == i );
+
+      res = std::from_chars(s1, s1_end, dbl, fmt);
+      VERIFY( res.ec == std::errc{} );
+      VERIFY( res.ptr == s1_end );
+      VERIFY( dbl == i );
+
+      res = std::from_chars(s1, s1_end, ldbl, fmt);
+      VERIFY( res.ec == std::errc{} );
+      VERIFY( res.ptr == s1_end );
+      VERIFY( ldbl == i );
+    }
+
+    if (i > 9)
+      continue;
+
+    // Test single-digit integers with small exponents.
+
+    const char s2[] = { '.', *s1, 'e', '0', '0', '0', '1' };
+    const char* s2_end = s2 + sizeof(s2);
+
+    const char s3[] = { *s1, '0', 'e', '-', '0', '0', '1' };
+    const char* s3_end = s3 + sizeof(s3);
+
+    for (auto fmt : { std::chars_format::scientific,
+                     std::chars_format::general })
+    {
+      res = std::from_chars(s2, s2_end, flt, fmt);
+      VERIFY( res.ec == std::errc{} );
+      VERIFY( res.ptr == s2_end );
+      VERIFY( flt == i );
+
+      res = std::from_chars(s3, s3_end, flt, fmt);
+      VERIFY( res.ec == std::errc{} );
+      VERIFY( res.ptr == s3_end );
+      VERIFY( flt == i );
+
+      res = std::from_chars(s2, s2_end, dbl, fmt);
+      VERIFY( res.ec == std::errc{} );
+      VERIFY( res.ptr == s2_end );
+      VERIFY( dbl == i );
+
+      res = std::from_chars(s3, s3_end, dbl, fmt);
+      VERIFY( res.ec == std::errc{} );
+      VERIFY( res.ptr == s3_end );
+      VERIFY( dbl == i );
+
+      res = std::from_chars(s2, s2_end, ldbl, fmt);
+      VERIFY( res.ec == std::errc{} );
+      VERIFY( res.ptr == s2_end );
+      VERIFY( ldbl == i );
+
+      res = std::from_chars(s3, s3_end, ldbl, fmt);
+      VERIFY( res.ec == std::errc{} );
+      VERIFY( res.ptr == s3_end );
+      VERIFY( ldbl == i );
+    }
+  }
+}
+
+template<typename FloatT, typename UIntT>
+void
+test_max_mantissa()
+{
+  using Float_limits = std::numeric_limits<FloatT>;
+  using UInt_limits = std::numeric_limits<UIntT>;
+
+  if constexpr (Float_limits::is_iec559
+               && Float_limits::digits < UInt_limits::digits)
+  {
+    std::printf("Testing %d-bit float, using %zu-bit integer\n",
+       Float_limits::digits + (int)std::log2(Float_limits::max_exponent) + 1,
+       sizeof(UIntT) * __CHAR_BIT__);
+
+    std::from_chars_result res;
+    FloatT flt;
+
+    for (int i = 0; i < 10; ++i)
+    {
+      // (1 << digits) - 1 is the maximum value of the mantissa
+      const auto val = ((UIntT)1 << Float_limits::digits) - 1 - i;
+      std::string s = to_string(val);
+      auto len = s.length();
+      s += "000"; // these should be ignored
+      for (auto fmt : { std::chars_format::fixed,
+                       std::chars_format::general })
+      {
+       res = std::from_chars(s.data(), s.data() + len, flt, fmt);
+       VERIFY( res.ec == std::errc{} );
+       VERIFY( res.ptr == s.data() + len );
+       VERIFY( flt == val );
+      }
+      s.resize(len);
+      const auto orig_len = len;
+      s += "e+000";
+      len = s.length();
+      s += "111";
+      for (auto fmt : { std::chars_format::scientific,
+                       std::chars_format::general })
+      {
+       res = std::from_chars(s.data(), s.data() + len, flt, fmt);
+       VERIFY( res.ec == std::errc{} );
+       VERIFY( res.ptr == s.data() + len );
+       VERIFY( flt == val );
+
+       std::string s2 = s.substr(0, len - 5);
+       s2.insert(s2.cbegin() + orig_len - 1, '.');
+       s2 += "e000000000001";
+       res = std::from_chars(s.data(), s.data() + len, flt, fmt);
+       VERIFY( res.ec == std::errc{} );
+       VERIFY( res.ptr == s.data() + len );
+       VERIFY( flt == val );
+      }
+    }
+  }
+}
+
+void
+test06()
+{
+  test_max_mantissa<float, unsigned long>();
+  test_max_mantissa<double, unsigned long long>();
+#ifdef __GLIBCXX_TYPE_INT_N_0
+  test_max_mantissa<long double, unsigned __GLIBCXX_TYPE_INT_N_0>();
+#endif
+}
+
+int
+main()
+{
+  test01();
+  test02();
+  test03();
+  test04();
+  test05();
+  test06();
+}
diff --git a/libstdc++-v3/testsuite/20_util/from_chars/5.cc b/libstdc++-v3/testsuite/20_util/from_chars/5.cc
new file mode 100644 (file)
index 0000000..f8fc7f6
--- /dev/null
@@ -0,0 +1,163 @@
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// <charconv> is supported in C++14 as a GNU extension
+// { dg-do run { target c++14 } }
+
+#include <charconv>
+#include <string>
+#include <cmath>
+#include <testsuite_hooks.h>
+
+// Test std::from_chars error handling.
+
+void
+test01()
+{
+  std::from_chars_result r;
+  double d = 3.2;
+  std::string s;
+
+  for (auto p : { "", "*", ".", "-", "-*", "-.", "+", "+.", "+-", "-+", "+1",
+                 ".p1", "-.p1",
+                 "in", "inch", "+inf", "na", "nam", "+nan" })
+  {
+    s = p;
+    for (auto fmt : { std::chars_format::fixed, std::chars_format::scientific,
+                     std::chars_format::general, std::chars_format::hex })
+    {
+      r = std::from_chars(s.data(), s.data() + s.length(), d, fmt);
+      VERIFY( r.ec == std::errc::invalid_argument );
+      VERIFY( r.ptr == s.data() );
+      VERIFY( d == 3.2 );
+    }
+  }
+
+  for (auto p : { ".e1", "-.e1" }) // These are valid patterns for hex format
+  {
+    s = p;
+    for (auto fmt : { std::chars_format::fixed, std::chars_format::scientific,
+                     std::chars_format::general })
+    {
+      r = std::from_chars(s.data(), s.data() + s.length(), d, fmt);
+      VERIFY( r.ec == std::errc::invalid_argument );
+      VERIFY( r.ptr == s.data() );
+      VERIFY( d == 3.2 );
+    }
+  }
+
+  // scientific format requires an exponent
+  for (auto p : { "1.2", "-1.2", "1.2e", "-1.2e", "1.2e-", "-1.2e+" })
+  {
+    s = p;
+    r = std::from_chars(s.data(), s.data() + s.length(), d,
+                       std::chars_format::scientific);
+    VERIFY( r.ec == std::errc::invalid_argument );
+    VERIFY( r.ptr == s.data() );
+    VERIFY( d == 3.2 );
+  }
+
+  // patterns that are invalid without the final character
+  for (auto p : { "1", ".1", "-1", "-.1",
+                 "inf", "-inf", "nan", "-nan" })
+  {
+    s = p;
+    for (auto fmt : { std::chars_format::fixed, std::chars_format::scientific,
+                     std::chars_format::general, std::chars_format::hex })
+    {
+      r = std::from_chars(s.data(), s.data() + s.length() - 1, d, fmt);
+      VERIFY( r.ec == std::errc::invalid_argument );
+      VERIFY( r.ptr == s.data() );
+      VERIFY( d == 3.2 );
+    }
+  }
+}
+
+void
+test02()
+{
+  std::from_chars_result r;
+  std::string s;
+
+  float f = 0.5;
+  // Overflow
+  s = "99999999999999999e999999999999999999";
+  r = std::from_chars(s.data(), s.data() + s.length(), f);
+  VERIFY( r.ec == std::errc::result_out_of_range );
+  VERIFY( r.ptr == s.data() + s.length() );
+  VERIFY( f == 0.5 );
+
+  s += '*';
+  r = std::from_chars(s.data(), s.data() + s.length(), f);
+  VERIFY( r.ec == std::errc::result_out_of_range );
+  VERIFY( r.ptr == s.data() + s.length() - 1 );
+  VERIFY( f == 0.5 );
+
+  s.insert(s.begin(), '-');
+  r = std::from_chars(s.data(), s.data() + s.length(), f);
+  VERIFY( r.ec == std::errc::result_out_of_range );
+  VERIFY( r.ptr == s.data() + s.length() - 1 );
+  VERIFY( f == 0.5 );
+}
+
+void
+test03()
+{
+  double d = 0.5;
+  // Underflow
+  std::string s("-1.2345e-9999zzz");
+  std::from_chars_result res;
+  res = std::from_chars(s.data(), s.data() + s.length(), d);
+  VERIFY( res.ptr == s.data() + s.length() - 3 );
+  VERIFY( res.ec == std::errc::result_out_of_range );
+  VERIFY( d == 0.5 );
+
+  res = std::from_chars(s.data() + 1, s.data() + s.length(), d);
+  VERIFY( res.ptr == s.data() + s.length() - 3 );
+  VERIFY( res.ec == std::errc::result_out_of_range );
+  VERIFY( d == 0.5 );
+}
+
+void
+test04()
+{
+  std::from_chars_result res;
+  std::string z(2000, '0');
+  // Invalid inputs for scientific format
+  for (const char* s : { "", "1", ".", ".0", ".5", "1e+", "1e+-1" })
+  {
+    for (auto len : { 0, 10, 100, 1000, 2000 })
+    {
+      auto str = z.substr(len) + s;
+      double d = 99.0;
+      res = std::from_chars(str.data(), str.data() + str.length(), d,
+                           std::chars_format::scientific);
+      VERIFY( res.ec == std::errc::invalid_argument );
+      VERIFY( res.ptr == str.data() );
+      VERIFY( d == 99.0 );
+    }
+  }
+}
+
+int
+main()
+{
+  test01();
+  test02();
+  test03();
+  test04();
+}
index aedb6561ed4cf0c97aa1101f7d25874330230fd8..fd8224b6789d23ad428ebd80a9fff16527bd5ade 100644 (file)
@@ -209,6 +209,8 @@ check_version(symbol& test, bool added)
       known_versions.push_back("GLIBCXX_3.4.26");
       known_versions.push_back("GLIBCXX_3.4.27");
       known_versions.push_back("GLIBCXX_3.4.28");
+      known_versions.push_back("GLIBCXX_3.4.29");
+      known_versions.push_back("GLIBCXX_LDBL_3.4.29");
       known_versions.push_back("CXXABI_1.3");
       known_versions.push_back("CXXABI_LDBL_1.3");
       known_versions.push_back("CXXABI_1.3.1");
@@ -240,7 +242,10 @@ check_version(symbol& test, bool added)
        test.version_status = symbol::incompatible;
 
       // Check that added symbols are added in the latest pre-release version.
-      bool latestp = (test.version_name == "GLIBCXX_3.4.28"
+      bool latestp = (test.version_name == "GLIBCXX_3.4.29"
+         // XXX remove next line when GLIBCXX_3.4.30 is added and baselines
+         // have been regenerated to include GLIBCXX_LDBL_3.4.29 symbols:
+                    || test.version_name == "GLIBCXX_LDBL_3.4.29"
                     || test.version_name == "CXXABI_1.3.12"
                     || test.version_name == "CXXABI_FLOAT128"
                     || test.version_name == "CXXABI_TM_1");