[PATCH v3] libstdc++: Provide formatter for vector<bool>::reference [PR109162]

Jonathan Wakely jwakely@redhat.com
Fri Apr 4 09:48:48 GMT 2025


On 04/04/25 11:20 +0200, Tomasz Kamiński wrote:
>This patch implement formatter for vector<bool>::reference which
>is part of P2286R8.
>
>To indicate partial support we define __glibcxx_format_ranges macro
>value 1, without defining __cpp_lib_format_ranges.
>
>To avoid including the whole content of the <format> header, we
>introduce new bits/formatfwd.h forward declares classes required
>for newly introduce formatter.
>
>The signatures of the user-facing parse and format method of the provided
>formatters deviate from the standard by constraining types of params:
>* _Bit_reference instead T satisfying is-vector-bool-reference<T>
>* _CharT is constrained __formatter::__char
>* basic_format_parse_context<_CharT> for parse argument
>* basic_format_context<_Out, _CharT> for format second argument
>The standard specifies last three of above as unconstrained types, which leads
>to formattable<vector<bool>::reference, char32_t> (and any other type as char)
>being true.

The code looks good, just some comments on the changelog and comments:

>	PR libstdc++/109162
>
>libstdc++-v3/ChangeLog:
>
>	* include/Makefile.am: Add bits/formatfwd.h.
>	* include/Makefile.in: Add bits/formatfwd.h.
>	* include/bits/version.def:
>	Define __glibcxx_format_ranges without corresponding std name.

The line above should start after "version.def:" rather than on a new
line.

>	* include/bits/version.h: Regenerate.
>	* include/std/format (basic_format_context, __format::__char):
>	Move declartions to bits/formatfwd.h.
>	(formatter<_Tp, _CharT>): Remove default argument for _CharT
>	parameter, now specified in forward declaration in bits/formatfwd.h.
>	* include/std/vector (formatter<_Bit_reference, _CharT>: Define.

Missing ')'

>	* include/bits/formatfwd.h: New file with forward declartions

Spelling: "declarations"

>	for bits of std/format.
>	* testsuite/23_containers/vector/bool/format.cc: New test.
>---
>Updated to use no_stdname in version.def.
>
> libstdc++-v3/include/Makefile.am              |  1 +
> libstdc++-v3/include/Makefile.in              |  1 +
> libstdc++-v3/include/bits/formatfwd.h         | 68 +++++++++++++++++++
> libstdc++-v3/include/bits/version.def         | 18 ++---
> libstdc++-v3/include/bits/version.h           |  9 +++
> libstdc++-v3/include/std/format               | 14 +---
> libstdc++-v3/include/std/vector               | 32 +++++++++
> .../23_containers/vector/bool/format.cc       | 67 ++++++++++++++++++
> 8 files changed, 189 insertions(+), 21 deletions(-)
> create mode 100644 libstdc++-v3/include/bits/formatfwd.h
> create mode 100644 libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
>
>diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am
>index 4dc771a540c..537774c2668 100644
>--- a/libstdc++-v3/include/Makefile.am
>+++ b/libstdc++-v3/include/Makefile.am
>@@ -195,6 +195,7 @@ bits_headers = \
> 	${bits_srcdir}/cow_string.h \
> 	${bits_srcdir}/deque.tcc \
> 	${bits_srcdir}/erase_if.h \
>+	${bits_srcdir}/formatfwd.h \
> 	${bits_srcdir}/forward_list.h \
> 	${bits_srcdir}/forward_list.tcc \
> 	${bits_srcdir}/fs_dir.h \
>diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in
>index 0e3d09b3a75..7b96b2207f8 100644
>--- a/libstdc++-v3/include/Makefile.in
>+++ b/libstdc++-v3/include/Makefile.in
>@@ -548,6 +548,7 @@ bits_freestanding = \
> @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/cow_string.h \
> @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/deque.tcc \
> @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/erase_if.h \
>+@GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/formatfwd.h \
> @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/forward_list.h \
> @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/forward_list.tcc \
> @GLIBCXX_HOSTED_TRUE@	${bits_srcdir}/fs_dir.h \
>diff --git a/libstdc++-v3/include/bits/formatfwd.h b/libstdc++-v3/include/bits/formatfwd.h
>new file mode 100644
>index 00000000000..5450ad1297f
>--- /dev/null
>+++ b/libstdc++-v3/include/bits/formatfwd.h
>@@ -0,0 +1,68 @@
>+// <format> Formatting -*- C++ -*-
>+
>+// Copyright The GNU Toolchain Authors.
>+//
>+// 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/>.
>+
>+/** @file include/format

This should be bits/formatfwd.h

>+ *  This is a Standard C++ Library header.

This should be the internal header comment instead:

  *  This is an internal header file, included by other library headers.
  *  Do not attempt to use it directly. @headername{format}

The @headername{xxx} doxygen macro expands to "Instead, include <xxx>."

>+ */
>+
>+#ifndef _GLIBCXX_FORMAT_FWD_H
>+#define _GLIBCXX_FORMAT_FWD_H 1
>+
>+#ifdef _GLIBCXX_SYSHDR
>+#pragma GCC system_header
>+#endif

We should normally include <bits/version.h> before checking any
__glibcxx_xxx feature test macro:

>+#ifdef __glibcxx_format // C++ >= 20 && HOSTED

If you want to avoid re-including bits/version.h here because it's
guaranteed that formatfwd.h is only included after it, please add a
comment before the #ifdef. Something like:

// <bits/version.h> must have been included before this header:


>+namespace std _GLIBCXX_VISIBILITY(default)
>+{
>+_GLIBCXX_BEGIN_NAMESPACE_VERSION
>+
>+  // [format.context], class template basic_format_context
>+  template<typename _Out, typename _CharT> class basic_format_context;
>+
>+  // [format.parse.ctx], class template basic_format_parse_context
>+  template<typename _CharT> class basic_format_parse_context;
>+
>+  // [format.formatter], formatter
>+  template<typename _Tp, typename _CharT = char> struct formatter;
>+
>+namespace __format
>+{
>+#ifdef _GLIBCXX_USE_WCHAR_T
>+  template<typename _CharT>
>+    concept __char = same_as<_CharT, char> || same_as<_CharT, wchar_t>;
>+#else
>+  template<typename _CharT>
>+    concept __char = same_as<_CharT, char>;
>+#endif
>+
>+  template<__char _CharT>
>+    struct __formatter_int;
>+}
>+
>+_GLIBCXX_END_NAMESPACE_VERSION
>+} // namespace std
>+#endif // __glibcxx_format
>+#pragma GCC diagnostic pop
>+#endif // _GLIBCXX_FORMAT_FWD_H
>diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
>index 8569d9fa0ad..8f609b469cc 100644
>--- a/libstdc++-v3/include/bits/version.def
>+++ b/libstdc++-v3/include/bits/version.def
>@@ -1406,18 +1406,18 @@ ftms = {
>   };
> };
>
>-// ftms = {
>-  // name = format_ranges;
>+ftms = {
>+  name = format_ranges;
>   // 202207 P2286R8 Formatting Ranges
>   // 202207 P2585R1 Improving default container formatting
>   // LWG3750 Too many papers bump __cpp_lib_format
>-  // TODO: #define __cpp_lib_format_ranges 202207L
>-  // values = {
>-    // v = 202207;
>-    // cxxmin = 23;
>-    // hosted = yes;
>-  // };
>-// };
>+  no_stdname = true; // TODO remove
>+  values = {
>+    v = 1; // TODO 202207
>+    cxxmin = 23;
>+    hosted = yes;
>+  };
>+};
>
> ftms = {
>   name = freestanding_algorithm;
>diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
>index f7c9849893d..f05c3fd13c0 100644
>--- a/libstdc++-v3/include/bits/version.h
>+++ b/libstdc++-v3/include/bits/version.h
>@@ -1555,6 +1555,15 @@
> #endif /* !defined(__cpp_lib_expected) && defined(__glibcxx_want_expected) */
> #undef __glibcxx_want_expected
>
>+#if !defined(__cpp_lib_format_ranges)
>+# if (__cplusplus >= 202100L) && _GLIBCXX_HOSTED
>+#  define __glibcxx_format_ranges 1L
>+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_format_ranges)
>+#  endif
>+# endif
>+#endif /* !defined(__cpp_lib_format_ranges) && defined(__glibcxx_want_format_ranges) */
>+#undef __glibcxx_want_format_ranges
>+
> #if !defined(__cpp_lib_freestanding_algorithm)
> # if (__cplusplus >= 202100L)
> #  define __glibcxx_freestanding_algorithm 202311L
>diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
>index 9ef719edcf0..01a53143d1c 100644
>--- a/libstdc++-v3/include/std/format
>+++ b/libstdc++-v3/include/std/format
>@@ -52,6 +52,7 @@
> #include <string_view>
> #include <string>
> #include <bits/monostate.h>
>+#include <bits/formatfwd.h>
> #include <bits/ranges_base.h>  // input_range, range_reference_t
> #include <bits/ranges_util.h>  // subrange
> #include <bits/ranges_algobase.h> // ranges::copy
>@@ -73,9 +74,6 @@ namespace std _GLIBCXX_VISIBILITY(default)
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
>-  // [format.context], class template basic_format_context
>-  template<typename _Out, typename _CharT> class basic_format_context;
>-
>   // [format.fmt.string], class template basic_format_string
>   template<typename _CharT, typename... _Args> struct basic_format_string;
>
>@@ -178,7 +176,7 @@ namespace __format
>   // [format.formatter], formatter
>
>   /// The primary template of std::formatter is disabled.
>-  template<typename _Tp, typename _CharT = char>
>+  template<typename _Tp, typename _CharT>
>     struct formatter
>     {
>       formatter() = delete; // No std::formatter specialization for this type.
>@@ -923,14 +921,6 @@ namespace __format
>     bool _M_hasval = false;
>   };
>
>-#ifdef _GLIBCXX_USE_WCHAR_T
>-  template<typename _CharT>
>-    concept __char = same_as<_CharT, char> || same_as<_CharT, wchar_t>;
>-#else
>-  template<typename _CharT>
>-    concept __char = same_as<_CharT, char>;
>-#endif
>-
>   template<__char _CharT>
>     struct __formatter_str
>     {
>diff --git a/libstdc++-v3/include/std/vector b/libstdc++-v3/include/std/vector
>index 0f043340fe5..9cf292e444b 100644
>--- a/libstdc++-v3/include/std/vector
>+++ b/libstdc++-v3/include/std/vector
>@@ -157,4 +157,36 @@ _GLIBCXX_END_NAMESPACE_VERSION
> } // namespace std
> #endif // __cpp_lib_erase_if
>
>+#ifdef __glibcxx_format_ranges // C++ >= 20 && HOSTED
>+#include <bits/formatfwd.h>
>+
>+namespace std _GLIBCXX_VISIBILITY(default)
>+{
>+_GLIBCXX_BEGIN_NAMESPACE_VERSION
>+  // Standard does not constrain accepted _CharT and declares it as formatter
>+  // of Tp that statisfies is-vector-bool-reference<T>,
>+  template<__format::__char _CharT>
>+    struct formatter<_GLIBCXX_STD_C::_Bit_reference, _CharT> {

New line before the '{' please, and the "public:" access-specifier is
redundant here:

>+    public:
>+      // Standard declares this as template accepting unconstrained
>+      // ParseContext type.
>+      constexpr typename basic_format_parse_context<_CharT>::iterator
>+      parse(basic_format_parse_context<_CharT>& __pc)
>+      { return _M_f.template _M_parse<bool>(__pc); }
>+
>+      // Standard declares this as template accepting unconstrained
>+      // FormatContext type.
>+      template<typename _Out>
>+	typename basic_format_context<_Out, _CharT>::iterator
>+	format(const _GLIBCXX_STD_C::_Bit_reference& __u,
>+	       basic_format_context<_Out, _CharT>& __fc) const
>+	{ return _M_f.format(static_cast<bool>(__u), __fc); }
>+
>+    private:
>+      __format::__formatter_int<_CharT> _M_f;
>+    };
>+_GLIBCXX_END_NAMESPACE_VERSION
>+} // namespace std
>+#endif // __glibcxx_format_ranges
>+
> #endif /* _GLIBCXX_VECTOR */
>diff --git a/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc b/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
>new file mode 100644
>index 00000000000..1935d06ff88
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
>@@ -0,0 +1,67 @@
>+// { dg-do run { target c++23 } }
>+// { dg-timeout-factor 2 }
>+
>+#include <format>
>+#include <vector>
>+#include <chrono> // For _Widen
>+#include <testsuite_hooks.h>
>+
>+static_assert(!std::formattable<std::vector<bool>::reference, int>);
>+static_assert(!std::formattable<std::vector<bool>::reference, char32_t>);
>+
>+template<typename... Args>
>+bool
>+is_format_string_for(const char* str, Args&&... args)
>+{
>+  try {
>+    (void) std::vformat(str, std::make_format_args(args...));
>+    return true;
>+  } catch (const std::format_error&) {
>+    return false;
>+  }
>+}
>+
>+#define WIDEN_(C, S) ::std::chrono::__detail::_Widen<C>(S, L##S)
>+#define WIDEN(S) WIDEN_(_CharT, S)
>+
>+void
>+test_format_string()
>+{
>+  std::vector<bool> v(1, true);
>+  VERIFY( !is_format_string_for("{:?}", v[0]) );
>+  VERIFY( !is_format_string_for("{:P}", v[0]) );
>+
>+  // width needs to integer type

Grammar: "needs to be"

>+  VERIFY( !is_format_string_for("{:{}}", v[0], 1.0f) );
>+}
>+
>+template<typename _CharT>
>+void
>+test_output()
>+{
>+  std::basic_string<_CharT> res;
>+  size_t size = 0;
>+  std::vector<bool> v{true, false};
>+
>+  res = std::format(WIDEN("{}"), v[0]);
>+  VERIFY( res == WIDEN("true") );
>+
>+  res = std::format(WIDEN("{:s}"), v[1]);
>+  VERIFY( res == WIDEN("false") );
>+
>+  res = std::format(WIDEN("{:d} {:#B} {:#o} {:#x}"), v[0], v[1], v[0], v[1]);
>+  VERIFY( res == WIDEN("1 0B0 01 0x0") );
>+
>+  res = std::format(WIDEN("{:{}}"), v[0], 6);
>+  VERIFY( res == WIDEN("true  ") );
>+
>+  res = std::format(WIDEN("{:=^#7X}"), v[1]);
>+  VERIFY( res == WIDEN("==0X0==") );
>+}
>+
>+int main()
>+{
>+  test_format_string();
>+  test_output<char>();
>+  test_output<wchar_t>();
>+}
>-- 
>2.48.1
>
>



More information about the Libstdc++ mailing list