[committed] libstdc++: Implement constexpr std::to_chars for C++23 (P2291R3)

Message ID 20221015202518.2687700-1-jwakely@redhat.com
State Repeat Merge
Headers
Series [committed] libstdc++: Implement constexpr std::to_chars for C++23 (P2291R3) |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

Jonathan Wakely Oct. 15, 2022, 8:25 p.m. UTC
  This has been approved for C++23.

I've just realised that the commit msg doesn't match the actual patch. I
had a consteval lambda in an earlier version, but I changed it to do:

+#if __cpp_lib_constexpr_charconv
+         if (std::__is_constant_evaluated())
+           return __table<_DecOnly>.__data[__c];
+#endif

So the code is unchanged for non-constexpr, and uses a global variable
template for constexpr evaluations. If P2647R0 gets accepted and applies
to C++23 mode then we can remove this and the static constexpr variable
will work for both cases.

Tested powerpc64le-linux. Pushed to trunk.

-- >8 --

Some of the helper functions use static constexpr local variables, which
is not permitted in a core constant expression. Removing the 'static'
seems to have negligible performance effect for __to_chars and
__to_chars_16. For __from_chars_alnum_to_val removing the 'static'
causes a significant performance impact for base 36 conversions. Use a
consteval lambda instead.

libstdc++-v3/ChangeLog:

	* include/bits/charconv.h (__to_chars_10_impl): Add constexpr
	for C++23. Remove 'static' from array.
	* include/std/charconv (__cpp_lib_constexpr_charconv): Define.
	(__to_chars, __to_chars_16): Remove 'static' from array, add
	constexpr.
	(__to_chars_10, __to_chars_8, __to_chars_2, __to_chars_i)
	(to_chars, __raise_and_add, __from_chars_pow2_base)
	(__from_chars_alnum, from_chars): Add constexpr.
	(__from_chars_alnum_to_val): Avoid local static during constant
	evaluation. Add constexpr.
	* include/std/version (__cpp_lib_constexpr_charconv): Define.
	* testsuite/20_util/from_chars/constexpr.cc: New test.
	* testsuite/20_util/to_chars/constexpr.cc: New test.
	* testsuite/20_util/to_chars/version.cc: New test.
---
 libstdc++-v3/include/bits/charconv.h          |   4 +-
 libstdc++-v3/include/std/charconv             |  41 +++--
 libstdc++-v3/include/std/version              |   1 +
 .../testsuite/20_util/from_chars/constexpr.cc |  57 ++++++
 .../testsuite/20_util/to_chars/constexpr.cc   | 172 ++++++++++++++++++
 .../testsuite/20_util/to_chars/version.cc     |  16 ++
 6 files changed, 275 insertions(+), 16 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/from_chars/constexpr.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/to_chars/constexpr.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/to_chars/version.cc
  

Comments

Jonathan Wakely Oct. 17, 2022, 8:48 a.m. UTC | #1
On Sat, 15 Oct 2022 at 21:26, Jonathan Wakely via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/charconv.h (__to_chars_10_impl): Add constexpr
>         for C++23. Remove 'static' from array.
>         * include/std/charconv (__cpp_lib_constexpr_charconv): Define.

I managed to define the feature test macro with the wrong value. Fixed
by the attached patch, pushed to trunk.
commit 0f4815502d8dac07579dc7a5a40c597a18291b4c
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Mon Oct 17 09:38:02 2022

    libstdc++: Fix value of __cpp_lib_constexpr_charconv
    
    libstdc++-v3/ChangeLog:
    
            * include/std/charconv (__cpp_lib_constexpr_charconv): Define to
            correct value.
            * include/std/version (__cpp_lib_constexpr_charconv): Likewise.
            * testsuite/20_util/to_chars/constexpr.cc: Check correct value.
            * testsuite/20_util/to_chars/version.cc: Likewise.

diff --git a/libstdc++-v3/include/std/charconv b/libstdc++-v3/include/std/charconv
index 4b6cc83a567..7aefdd3298c 100644
--- a/libstdc++-v3/include/std/charconv
+++ b/libstdc++-v3/include/std/charconv
@@ -51,7 +51,7 @@
 #endif
 
 #if __cplusplus > 202002L
-# define __cpp_lib_constexpr_charconv 202202L
+# define __cpp_lib_constexpr_charconv 202207L
 #endif
 
 namespace std _GLIBCXX_VISIBILITY(default)
diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
index bec9e7aa792..3c7c440bd80 100644
--- a/libstdc++-v3/include/std/version
+++ b/libstdc++-v3/include/std/version
@@ -302,7 +302,7 @@
 #if __cplusplus > 202002L
 // c++23
 #define __cpp_lib_byteswap 202110L
-#define __cpp_lib_constexpr_charconv 202202L
+#define __cpp_lib_constexpr_charconv 202207L
 #define __cpp_lib_constexpr_typeinfo 202106L
 #if __cpp_concepts >= 202002L
 # define __cpp_lib_expected 202202L
diff --git a/libstdc++-v3/testsuite/20_util/to_chars/constexpr.cc b/libstdc++-v3/testsuite/20_util/to_chars/constexpr.cc
index 30c591659ee..10855b737c7 100644
--- a/libstdc++-v3/testsuite/20_util/to_chars/constexpr.cc
+++ b/libstdc++-v3/testsuite/20_util/to_chars/constexpr.cc
@@ -5,7 +5,7 @@
 
 #ifndef __cpp_lib_constexpr_charconv
 # error "Feature-test macro for constexpr charconv missing in <charconv>"
-#elif __cpp_lib_constexpr_charconv != 202202L
+#elif __cpp_lib_constexpr_charconv != 202207L
 # error "Feature-test macro for constexpr charconv has wrong value in <charconv>"
 #endif
 
diff --git a/libstdc++-v3/testsuite/20_util/to_chars/version.cc b/libstdc++-v3/testsuite/20_util/to_chars/version.cc
index af06e1bf054..25b1e0036e8 100644
--- a/libstdc++-v3/testsuite/20_util/to_chars/version.cc
+++ b/libstdc++-v3/testsuite/20_util/to_chars/version.cc
@@ -11,6 +11,6 @@
 
 #ifndef __cpp_lib_constexpr_charconv
 # error "Feature-test macro for constexpr charconv missing in <version>"
-#elif __cpp_lib_constexpr_charconv != 202202L
+#elif __cpp_lib_constexpr_charconv != 202207L
 # error "Feature-test macro for constexpr charconv has wrong value in <version>"
 #endif
  

Patch

diff --git a/libstdc++-v3/include/bits/charconv.h b/libstdc++-v3/include/bits/charconv.h
index 4cae10a72f7..d04aab77624 100644
--- a/libstdc++-v3/include/bits/charconv.h
+++ b/libstdc++-v3/include/bits/charconv.h
@@ -68,13 +68,13 @@  namespace __detail
   // The caller is required to provide a buffer of exactly the right size
   // (which can be determined by the __to_chars_len function).
   template<typename _Tp>
-    void
+    _GLIBCXX23_CONSTEXPR void
     __to_chars_10_impl(char* __first, unsigned __len, _Tp __val) noexcept
     {
       static_assert(is_integral<_Tp>::value, "implementation bug");
       static_assert(is_unsigned<_Tp>::value, "implementation bug");
 
-      static constexpr char __digits[201] =
+      constexpr char __digits[201] =
 	"0001020304050607080910111213141516171819"
 	"2021222324252627282930313233343536373839"
 	"4041424344454647484950515253545556575859"
diff --git a/libstdc++-v3/include/std/charconv b/libstdc++-v3/include/std/charconv
index 64d0584a55d..4b6cc83a567 100644
--- a/libstdc++-v3/include/std/charconv
+++ b/libstdc++-v3/include/std/charconv
@@ -50,6 +50,10 @@ 
 # define __cpp_lib_to_chars 201611L
 #endif
 
+#if __cplusplus > 202002L
+# define __cpp_lib_constexpr_charconv 202202L
+#endif
+
 namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
@@ -119,7 +123,7 @@  namespace __detail
 
   // Generic implementation for arbitrary bases.
   template<typename _Tp>
-    to_chars_result
+    constexpr to_chars_result
     __to_chars(char* __first, char* __last, _Tp __val, int __base) noexcept
     {
       static_assert(is_integral<_Tp>::value, "implementation bug");
@@ -138,7 +142,7 @@  namespace __detail
 
       unsigned __pos = __len - 1;
 
-      static constexpr char __digits[] = {
+      constexpr char __digits[] = {
 	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
 	'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
 	'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
@@ -160,7 +164,7 @@  namespace __detail
     }
 
   template<typename _Tp>
-    __integer_to_chars_result_type<_Tp>
+    constexpr __integer_to_chars_result_type<_Tp>
     __to_chars_16(char* __first, char* __last, _Tp __val) noexcept
     {
       static_assert(is_integral<_Tp>::value, "implementation bug");
@@ -177,7 +181,7 @@  namespace __detail
 	  return __res;
 	}
 
-      static constexpr char __digits[] = {
+      constexpr char __digits[] = {
 	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
 	'a', 'b', 'c', 'd', 'e', 'f'
       };
@@ -207,7 +211,7 @@  namespace __detail
     }
 
   template<typename _Tp>
-    inline __integer_to_chars_result_type<_Tp>
+    constexpr __integer_to_chars_result_type<_Tp>
     __to_chars_10(char* __first, char* __last, _Tp __val) noexcept
     {
       static_assert(is_integral<_Tp>::value, "implementation bug");
@@ -231,7 +235,7 @@  namespace __detail
     }
 
   template<typename _Tp>
-    __integer_to_chars_result_type<_Tp>
+    constexpr __integer_to_chars_result_type<_Tp>
     __to_chars_8(char* __first, char* __last, _Tp __val) noexcept
     {
       static_assert(is_integral<_Tp>::value, "implementation bug");
@@ -285,7 +289,7 @@  namespace __detail
     }
 
   template<typename _Tp>
-    __integer_to_chars_result_type<_Tp>
+    constexpr __integer_to_chars_result_type<_Tp>
     __to_chars_2(char* __first, char* __last, _Tp __val) noexcept
     {
       static_assert(is_integral<_Tp>::value, "implementation bug");
@@ -322,7 +326,7 @@  namespace __detail
 } // namespace __detail
 
   template<typename _Tp>
-    __detail::__integer_to_chars_result_type<_Tp>
+    constexpr __detail::__integer_to_chars_result_type<_Tp>
     __to_chars_i(char* __first, char* __last, _Tp __value, int __base = 10)
     {
       __glibcxx_assert(2 <= __base && __base <= 36);
@@ -361,7 +365,7 @@  namespace __detail
     }
 
 #define _GLIBCXX_TO_CHARS(T) \
-  inline to_chars_result \
+  _GLIBCXX23_CONSTEXPR inline to_chars_result \
   to_chars(char* __first, char* __last, T __value, int __base = 10) \
   { return std::__to_chars_i<T>(__first, __last, __value, __base); }
 _GLIBCXX_TO_CHARS(char)
@@ -400,7 +404,7 @@  _GLIBCXX_TO_CHARS(unsigned __GLIBCXX_TYPE_INT_N_3)
 namespace __detail
 {
   template<typename _Tp>
-    bool
+    constexpr bool
     __raise_and_add(_Tp& __val, int __base, unsigned char __c)
     {
       if (__builtin_mul_overflow(__val, __base, &__val)
@@ -429,18 +433,27 @@  namespace __detail
     return __table;
   }
 
+#if __cpp_lib_constexpr_charconv
+  template<bool _DecOnly>
+    inline constexpr auto __table = __from_chars_alnum_to_val_table();
+#endif
+
   // If _DecOnly is true: if the character is a decimal digit, then
   // return its corresponding base-10 value, otherwise return a value >= 127.
   // If _DecOnly is false: if the character is an alphanumeric digit, then
   // return its corresponding base-36 value, otherwise return a value >= 127.
   template<bool _DecOnly = false>
-    unsigned char
+    _GLIBCXX23_CONSTEXPR unsigned char
     __from_chars_alnum_to_val(unsigned char __c)
     {
       if _GLIBCXX17_CONSTEXPR (_DecOnly)
 	return static_cast<unsigned char>(__c - '0');
       else
 	{
+#if __cpp_lib_constexpr_charconv
+	  if (std::__is_constant_evaluated())
+	    return __table<_DecOnly>.__data[__c];
+#endif
 	  // This initializer is deliberately made dependent in order to work
 	  // around modules bug PR105322.
 	  static constexpr auto __table = (_DecOnly, __from_chars_alnum_to_val_table());
@@ -451,7 +464,7 @@  namespace __detail
   /// std::from_chars implementation for integers in a power-of-two base.
   /// If _DecOnly is true, then we may assume __base is at most 8.
   template<bool _DecOnly, typename _Tp>
-    bool
+    _GLIBCXX23_CONSTEXPR bool
     __from_chars_pow2_base(const char*& __first, const char* __last, _Tp& __val,
 			   int __base)
     {
@@ -508,7 +521,7 @@  namespace __detail
   /// std::from_chars implementation for integers in any base.
   /// If _DecOnly is true, then we may assume __base is at most 10.
   template<bool _DecOnly, typename _Tp>
-    bool
+    constexpr bool
     __from_chars_alnum(const char*& __first, const char* __last, _Tp& __val,
 		       int __base)
     {
@@ -548,7 +561,7 @@  namespace __detail
 
   /// std::from_chars for integral types.
   template<typename _Tp>
-    __detail::__integer_from_chars_result_type<_Tp>
+    _GLIBCXX23_CONSTEXPR __detail::__integer_from_chars_result_type<_Tp>
     from_chars(const char* __first, const char* __last, _Tp& __value,
 	       int __base = 10)
     {
diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version
index 397a4aa7b0a..bec9e7aa792 100644
--- a/libstdc++-v3/include/std/version
+++ b/libstdc++-v3/include/std/version
@@ -302,6 +302,7 @@ 
 #if __cplusplus > 202002L
 // c++23
 #define __cpp_lib_byteswap 202110L
+#define __cpp_lib_constexpr_charconv 202202L
 #define __cpp_lib_constexpr_typeinfo 202106L
 #if __cpp_concepts >= 202002L
 # define __cpp_lib_expected 202202L
diff --git a/libstdc++-v3/testsuite/20_util/from_chars/constexpr.cc b/libstdc++-v3/testsuite/20_util/from_chars/constexpr.cc
new file mode 100644
index 00000000000..6e146947c1f
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/from_chars/constexpr.cc
@@ -0,0 +1,57 @@ 
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <charconv>
+#include <testsuite_hooks.h>
+
+constexpr bool
+test()
+{
+  const char str[] = "-01234afz###";
+  const char* end = str + sizeof(str);
+
+  std::from_chars_result res;
+  int ival = 99;
+  unsigned uval = 99;
+
+  res = std::from_chars(str, str+1, ival, 10);
+  VERIFY( res.ptr == str );
+  VERIFY( res.ec == std::errc::invalid_argument );
+  VERIFY( ival == 99 );
+  res = std::from_chars(str, str+4, ival, 10);
+  VERIFY( res.ptr == str+4 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( ival == -12 );
+  res = std::from_chars(str, end, ival, 10);
+  VERIFY( res.ptr == str+6 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( ival == -1234 );
+
+  res = std::from_chars(str, end, uval, 10);
+  VERIFY( res.ptr == str );
+  VERIFY( res.ec == std::errc::invalid_argument );
+  VERIFY( uval == 99 );
+  res = std::from_chars(str+1, end, uval, 10);
+  VERIFY( res.ptr == str+6 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( uval == 1234 );
+
+  res = std::from_chars(str, end, ival, 3);
+  VERIFY( res.ptr == str+4 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( ival == -5 );
+
+  res = std::from_chars(str, end, ival, 16);
+  VERIFY( res.ptr == str+8 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( ival == -1193135 );
+
+  res = std::from_chars(str+1, end, uval, 36);
+  VERIFY( res.ptr == str+1+8 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( uval == 2302953695 );
+
+  return true;
+}
+
+static_assert( test() );
diff --git a/libstdc++-v3/testsuite/20_util/to_chars/constexpr.cc b/libstdc++-v3/testsuite/20_util/to_chars/constexpr.cc
new file mode 100644
index 00000000000..30c591659ee
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/to_chars/constexpr.cc
@@ -0,0 +1,172 @@ 
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+
+#include <charconv>
+
+#ifndef __cpp_lib_constexpr_charconv
+# error "Feature-test macro for constexpr charconv missing in <charconv>"
+#elif __cpp_lib_constexpr_charconv != 202202L
+# error "Feature-test macro for constexpr charconv has wrong value in <charconv>"
+#endif
+
+#include <testsuite_hooks.h>
+
+constexpr bool
+test_base10()
+{
+  std::to_chars_result res;
+  char buf[10] = "XXXXXXXXX";
+  res = std::to_chars(buf, buf+3, 1234);
+  VERIFY( res.ptr == buf+3 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+4, -1234);
+  VERIFY( res.ptr == buf+4 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+4, 1234);
+  VERIFY( res.ptr == buf+4 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == '1' );
+  VERIFY( buf[1] == '2' );
+  VERIFY( buf[2] == '3' );
+  VERIFY( buf[3] == '4' );
+  VERIFY( buf[4] == 'X' );
+  res = std::to_chars(buf, buf+10, -567, 10);
+  VERIFY( res.ptr == buf+4 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == '-' );
+  VERIFY( buf[1] == '5' );
+  VERIFY( buf[2] == '6' );
+  VERIFY( buf[3] == '7' );
+  VERIFY( buf[4] == 'X' );
+  return true;
+}
+
+static_assert( test_base10() );
+
+constexpr bool
+test_base16()
+{
+  std::to_chars_result res;
+  char buf[10] = "XXXXXXXXX";
+  res = std::to_chars(buf, buf+3, 0x1234, 16);
+  VERIFY( res.ptr == buf+3 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+4, -0x1234, 16);
+  VERIFY( res.ptr == buf+4 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+4, 0x1234, 16);
+  VERIFY( res.ptr == buf+4 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == '1' );
+  VERIFY( buf[1] == '2' );
+  VERIFY( buf[2] == '3' );
+  VERIFY( buf[3] == '4' );
+  VERIFY( buf[4] == 'X' );
+  res = std::to_chars(buf, buf+10, -0x567, 16);
+  VERIFY( res.ptr == buf+4 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == '-' );
+  VERIFY( buf[1] == '5' );
+  VERIFY( buf[2] == '6' );
+  VERIFY( buf[3] == '7' );
+  VERIFY( buf[5] == 'X' );
+  return true;
+}
+
+static_assert( test_base16() );
+
+constexpr bool
+test_base8()
+{
+  std::to_chars_result res;
+  char buf[10] = "XXXXXXXXX";
+  res = std::to_chars(buf, buf+2, 01234, 8);
+  VERIFY( res.ptr == buf+2 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+3, -01234, 8);
+  VERIFY( res.ptr == buf+3 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+4, 01234, 8);
+  VERIFY( res.ptr == buf+4 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == '1' );
+  VERIFY( buf[1] == '2' );
+  VERIFY( buf[2] == '3' );
+  VERIFY( buf[3] == '4' );
+  VERIFY( buf[4] == 'X' );
+  res = std::to_chars(buf, buf+10, -0567, 8);
+  VERIFY( res.ptr == buf+4 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == '-' );
+  VERIFY( buf[1] == '5' );
+  VERIFY( buf[2] == '6' );
+  VERIFY( buf[3] == '7' );
+  VERIFY( buf[4] == 'X' );
+  return true;
+}
+
+static_assert( test_base8() );
+
+constexpr bool
+test_base2()
+{
+  std::to_chars_result res;
+  char buf[10] = "XXXXXXXXX";
+  res = std::to_chars(buf, buf+4, 0b10001, 2);
+  VERIFY( res.ptr == buf+4 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+5, -0b10001, 2);
+  VERIFY( res.ptr == buf+5 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+5, 0b10001, 2);
+  VERIFY( res.ptr == buf+5 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == '1' );
+  VERIFY( buf[1] == '0' );
+  VERIFY( buf[2] == '0' );
+  VERIFY( buf[3] == '0' );
+  VERIFY( buf[4] == '1' );
+  VERIFY( buf[5] == 'X' );
+  res = std::to_chars(buf, buf+10, -0b11011, 2);
+  VERIFY( res.ptr == buf+6 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == '-' );
+  VERIFY( buf[1] == '1' );
+  VERIFY( buf[2] == '1' );
+  VERIFY( buf[3] == '0' );
+  VERIFY( buf[4] == '1' );
+  VERIFY( buf[5] == '1' );
+  VERIFY( buf[6] == 'X' );
+  return true;
+}
+
+static_assert( test_base2() );
+
+constexpr bool
+test_base36()
+{
+  std::to_chars_result res;
+  char buf[10] = "XXXXXXXXX";
+  res = std::to_chars(buf, buf+1, 1234, 36);
+  VERIFY( res.ptr == buf+1 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+2, -1234, 36);
+  VERIFY( res.ptr == buf+2 );
+  VERIFY( res.ec == std::errc::value_too_large );
+  res = std::to_chars(buf, buf+3, 1234, 36);
+  VERIFY( res.ptr == buf+2 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == 'y' );
+  VERIFY( buf[1] == 'a' );
+  VERIFY( buf[3] == 'X' );
+  res = std::to_chars(buf, buf+10, -567, 36);
+  VERIFY( res.ptr == buf+3 );
+  VERIFY( res.ec == std::errc{} );
+  VERIFY( buf[0] == '-' );
+  VERIFY( buf[1] == 'f' );
+  VERIFY( buf[2] == 'r' );
+  VERIFY( buf[4] == 'X' );
+  return true;
+}
+
+static_assert( test_base36() );
diff --git a/libstdc++-v3/testsuite/20_util/to_chars/version.cc b/libstdc++-v3/testsuite/20_util/to_chars/version.cc
new file mode 100644
index 00000000000..af06e1bf054
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/to_chars/version.cc
@@ -0,0 +1,16 @@ 
+// { dg-options "-std=gnu++23" }
+// { dg-do preprocess { target c++23 } }
+
+#include <version>
+
+#ifndef __cpp_lib_to_chars
+# error "Feature-test macro for to_chars missing in <version>"
+#elif __cpp_lib_to_chars != 201611L
+# error "Feature-test macro for to_chars has wrong value in <version>"
+#endif
+
+#ifndef __cpp_lib_constexpr_charconv
+# error "Feature-test macro for constexpr charconv missing in <version>"
+#elif __cpp_lib_constexpr_charconv != 202202L
+# error "Feature-test macro for constexpr charconv has wrong value in <version>"
+#endif