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 an 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 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.
---
The description of this patch is longer that then change, but I think
to record why I have choosen particular direction.

Tested on x86_64-linux locally. OK for trunk?

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

diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index d63c6fc9efd..e6377aaa2ed 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, _Iter_for_t<_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 00000000000..1358fc11052
--- /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>);
+
-- 
2.51.0

Reply via email to