[gcc r16-3738] libstdc++: Use _Drop_iter<_CharT> for formattable concept checking [PR121765]

Tomasz Kaminski tkaminsk@gcc.gnu.org
Wed Sep 10 09:50:37 GMT 2025


https://gcc.gnu.org/g:ac1665954058f355aab11e1b48b1176c7e0af363

commit r16-3738-gac1665954058f355aab11e1b48b1176c7e0af363
Author: Tomasz Kamiński <tkaminsk@redhat.com>
Date:   Thu Sep 4 13:58:23 2025 +0200

    libstdc++: Use _Drop_iter<_CharT> for formattable concept checking [PR121765]
    
    When producing output, the libstdc++ format implementation only uses _Sink_iter
    specializations. Since users cannot construct basic_format_context, this is the
    only iterator type actually used. The __format_padded helper relies on this
    property to efficiently pad sequences from tuples and ranges.
    
    However, the standard's formattable concept requires a generic format function
    in formatters that works with any iterator type. This is intended to
    future-proof the implementation by allowing new format_context types. Previously,
    libstdc++ used back_insert_iterator<basic_string<_CharT>> for this purpose.
    
    Normally, concept checks only instantiate function signatures, but with
    user-defined formatters and deduced return types, the function body and all
    called functions are instantiated. This could trigger a static assertion error
    in the range/tuple formatter that assumed the iterator was a _Sink_iter
    (see included test).
    
    This patch resolves the issue by replacing the _Iter_for_t alias with the
    internal _Drop_iter. This iterator's sematnics is to drop elements, so
    __format_padded can handle it by simply returning the input iterator, which
    still produces the required behavior [1].
    
    An alternative of using _Sink_iter was considered but rejected because it would
    allow formatters to pass formattable requirements while only supporting
    format_context and wformat_context, which seems counter to the design intent
    (the std/format/formatter/concept.cc fails).
    
    [1] The standard's wording defines format functions as producing an output
    representation, but does not explicitly require a formatter to be invoked
    for each element. This allows the use of _Drop_iter to pass the concept check
    without generating any output.
    
            PR libstdc++/121765
    
    libstdc++-v3/ChangeLog:
    
            * include/std/format (__format::_Drop_iter): Define.
            (_Iter_for_t::type): Change alias to _Drop_iter.
            (__format::__format_padded): Return __fc.out() for
            _Drop_iter.
            * testsuite/std/format/pr121765.cc: New test.
    
    Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
    Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>

Diff:
---
 libstdc++-v3/include/std/format               | 120 ++++++++++++++++++--------
 libstdc++-v3/testsuite/std/format/pr121765.cc |  53 ++++++++++++
 2 files changed, 136 insertions(+), 37 deletions(-)

diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index d63c6fc9efd5..d6a2170e45d8 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -56,7 +56,7 @@
 #include <bits/ranges_base.h>  // input_range, range_reference_t
 #include <bits/ranges_util.h>  // subrange
 #include <bits/ranges_algobase.h> // ranges::copy
-#include <bits/stl_iterator.h> // back_insert_iterator, counted_iterator
+#include <bits/stl_iterator.h> // counted_iterator
 #include <bits/stl_pair.h>     // __is_pair
 #include <bits/unicode.h>      // __is_scalar_value, _Utf_view, etc.
 #include <bits/utility.h>      // tuple_size_v
@@ -110,10 +110,14 @@ namespace __format
   template<typename _CharT>
     class _Sink_iter;
 
+  // Output iterator that ignores the characters
+  template<typename _CharT>
+    class _Drop_iter;
+
   // An unspecified output iterator type used in the `formattable` concept.
   template<typename _CharT>
     struct _Iter_for
-    { using type = back_insert_iterator<basic_string<_CharT>>; };
+    { using type = _Drop_iter<_CharT>; };
 
   template<typename _CharT>
     using __format_context = basic_format_context<_Sink_iter<_CharT>, _CharT>;
@@ -3135,6 +3139,43 @@ _GLIBCXX_END_NAMESPACE_CONTAINER
 /// @cond undocumented
 namespace __format
 {
+  template<typename _CharT>
+    class _Drop_iter
+    {
+    public:
+      using iterator_category = output_iterator_tag;
+      using value_type = void;
+      using difference_type = ptrdiff_t;
+      using pointer = void;
+      using reference = void;
+
+      _Drop_iter() = default;
+      _Drop_iter(const _Drop_iter&) = default;
+      _Drop_iter& operator=(const _Drop_iter&) = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Drop_iter&
+      operator=(_CharT __c)
+      { return *this;  }
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Drop_iter&
+      operator=(basic_string_view<_CharT> __s)
+      { return *this; }
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Drop_iter&
+      operator*() { return *this; }
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Drop_iter&
+      operator++() { return *this; }
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Drop_iter
+      operator++(int) { return *this; }
+    };
+
   template<typename _CharT>
     class _Sink_iter
     {
@@ -5503,42 +5544,47 @@ namespace __format
 		    const _Spec<_CharT>& __spec,
 		    _Callback&& __call)
     {
-      // This is required to implement formatting with padding,
-      // as we need to format to temporary buffer, using the same iterator.
-      static_assert(is_same_v<_Out, __format::_Sink_iter<_CharT>>);
-
-      const size_t __padwidth = __spec._M_get_width(__fc);
-      if (__padwidth == 0)
-	return __call(__fc);
-
-      struct _Restore_out
-      {
-	_Restore_out(basic_format_context<_Sink_iter<_CharT>, _CharT>& __fc)
-	: _M_ctx(std::addressof(__fc)), _M_out(__fc.out())
-	{ }
-
-	void
-	_M_disarm()
-	{ _M_ctx = nullptr; }
-
-	~_Restore_out()
-	{
-	  if (_M_ctx)
-	    _M_ctx->advance_to(_M_out);
-	}
-
-      private:
-	basic_format_context<_Sink_iter<_CharT>, _CharT>* _M_ctx;
-	_Sink_iter<_CharT> _M_out;
-      };
+      if constexpr (is_same_v<_Out, _Drop_iter<_CharT>>)
+        return __fc.out();
+      else
+        {
+          // This is required to implement formatting with padding,
+          // as we need to format to temporary buffer, using the same iterator.
+          static_assert(is_same_v<_Out, _Sink_iter<_CharT>>);
+
+         const size_t __padwidth = __spec._M_get_width(__fc);
+         if (__padwidth == 0)
+	   return __call(__fc);
+
+         struct _Restore_out
+         {
+	   _Restore_out(basic_format_context<_Sink_iter<_CharT>, _CharT>& __fc)
+	   : _M_ctx(std::addressof(__fc)), _M_out(__fc.out())
+	  { }
+
+	  void
+	  _M_disarm()
+	  { _M_ctx = nullptr; }
+
+	  ~_Restore_out()
+	  {
+	    if (_M_ctx)
+	      _M_ctx->advance_to(_M_out);
+	  }
 
-      _Restore_out __restore(__fc);
-      _Padding_sink<_Sink_iter<_CharT>, _CharT> __sink(__fc.out(), __padwidth);
-      __fc.advance_to(__sink.out());
-      __call(__fc);
-      __fc.advance_to(__sink._M_finish(__spec._M_align, __spec._M_fill));
-      __restore._M_disarm();
-      return __fc.out();
+        private:
+	  basic_format_context<_Sink_iter<_CharT>, _CharT>* _M_ctx;
+	  _Sink_iter<_CharT> _M_out;
+        };
+
+        _Restore_out __restore(__fc);
+        _Padding_sink<_Sink_iter<_CharT>, _CharT> __sink(__fc.out(), __padwidth);
+        __fc.advance_to(__sink.out());
+        __call(__fc);
+        __fc.advance_to(__sink._M_finish(__spec._M_align, __spec._M_fill));
+        __restore._M_disarm();
+        return __fc.out();
+      }
     }
 
   template<size_t _Pos, typename _Tp, typename _CharT>
diff --git a/libstdc++-v3/testsuite/std/format/pr121765.cc b/libstdc++-v3/testsuite/std/format/pr121765.cc
new file mode 100644
index 000000000000..1358fc110521
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/pr121765.cc
@@ -0,0 +1,53 @@
+// { dg-do compile { target c++23 } }
+
+#include <format>
+#include <utility>
+
+struct MyPair 
+{
+  int x;
+  int y;
+};
+
+template<typename CharT>
+struct std::formatter<MyPair, CharT> 
+{
+  template<typename ParseContext>
+  auto parse(ParseContext& pc)
+  { return _formatter.parse(pc);  }
+
+  template<typename FormatContext>
+  auto format(const MyPair& mp, FormatContext& fc) const
+  { return _formatter.format(std::make_pair(mp.x, mp.y), fc); }
+
+private:
+  std::formatter<std::pair<int, int>, CharT> _formatter;
+};
+
+static_assert(std::formattable<MyPair, char>);
+static_assert(std::formattable<MyPair, wchar_t>);
+
+struct MyRange
+{
+  int* begin;
+  int* end;
+};
+
+template<typename CharT>
+struct std::formatter<MyRange, CharT> 
+{
+  template<typename ParseContext>
+  auto parse(ParseContext& pc)
+  { return _formatter.parse(pc);  }
+
+  template<typename FormatContext>
+  auto format(const MyRange& mp, FormatContext& fc) const
+  { return _formatter.format(std::span<int>(mp.begin, mp.end), fc); }
+
+private:
+  std::formatter<std::span<int>, CharT> _formatter;
+};
+
+static_assert(std::formattable<MyRange, char>);
+static_assert(std::formattable<MyRange, wchar_t>);
+


More information about the Libstdc++-cvs mailing list