[PATCH v2] libstdc++: Export explicit instantiations of __format::__do_vformat_to.

Jonathan Wakely jwakely@redhat.com
Thu Apr 16 09:42:24 GMT 2026


On Thu, 16 Apr 2026 at 10:41, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Thu, Apr 16, 2026 at 11:34 AM Jonathan Wakely <jwakely@redhat.com> wrote:
>>
>> On Fri, 10 Apr 2026 at 14:28 +0200, Tomasz Kamiński wrote:
>> >This patch extracts an __format::__do_vformat_to for the _Sink_iter
>>
>> s/an/a/
>>
>> >(and matching format context) and exports explicit instantiations
>> >of it for wchar and wchar_t. As every format function is implementing
>>
>> s/wchar and wchar_t/char and wchar_t/
>>
>> >as delegating the one of these overloads, this significantly reduces
>>
>> s/the one/ to one/
>>
>> >the compliation time.
>> >
>> >Instantiating __format::__do_vformat_to triggers specializations of
>> >formatters for types stored in handle direclty (arithmetic types,
>>
>> s/direclty/directly/
>>
>> Do you mean stored in basic_format_arg directly? They're not stored in
>> a handle, right?
>
> Yes, in basic_format_arg.
>>
>>
>> >strings). In case when their behavior depends on the TU specific
>> >configuration, only one configuration can be exported from shared
>> >lib. In case of beforementioned formatter:
>> >* ? (debug mode) is accepted in for strings and characters
>> >* multibyte utf-8 encoded character is accepted as fill when the
>> >  literal encoding is Unicode.
>> >
>> >The first issue is addressed by this patch, by declaring extern
>> >defintion only for the C++20. We will need to reconsider how to
>> >handle the specifiers when C++23 becomes stable.
>> >
>> >The literal encoding is handled by adding a second template parameter
>> >to __do_vformat_to overload, that is initialzied with 1 if literal
>>
>> s/initialzied/initialized/
>>
>> >encoding is unicode (the parameter has unsigned type to allow more
>> >information to be encoded). This allows library to export implementation
>> >for unicode literal encoding (format-inst.cc is compiled with appropariate
>> >flag), by declaring extern specialization only for 1 value.
>>
>> I like this solution. With the default UTF-8 encoding, most users will
>> get the benefits of the extern templates.
>>
>> >libstdc++-v3/ChangeLog:
>> >
>> >       * config/abi/pre/gnu.ver (GLIBCXX_3.4): Exclude exports
>> >       of std::basic_fo* (matching basic_format_context).
>> >       (GLIBCXX_3.4.35): Export __format::__do_vformat_to
>> >       specializations for _Sink_iter and char/wchar_t.
>> >       * include/std/format: (__format::__do_vformat_to):
>> >       Extract overload accepting _Sink_iter and provide extern
>> >       explicit specialization for char/wchar_t in C++20 mode.
>> >       * src/c++20/Makefile.am: Add format-inst.cc.
>> >       * src/c++20/Makefile.in: Regenerate.
>> >       * src/c++20/format-inst.cc: New file defining explicit
>> >       instantiation.
>> >---
>> >Sending v2 that addresses handling program using non-unicode literal
>> >encoding linking with libstdc++. I am starting to lean more into
>> >this solution, in combination with accepting later format-string
>> >additions as extension being better direction.
>> >
>> >Tested on x86_64-linux.
>> >
>> >
>> > libstdc++-v3/config/abi/pre/gnu.ver   |   8 +-
>> > libstdc++-v3/include/std/format       | 154 +++++++++++++++-----------
>> > libstdc++-v3/src/c++20/Makefile.am    |  10 +-
>> > libstdc++-v3/src/c++20/Makefile.in    |  18 ++-
>> > libstdc++-v3/src/c++20/format-inst.cc |  46 ++++++++
>> > 5 files changed, 163 insertions(+), 73 deletions(-)
>> > create mode 100644 libstdc++-v3/src/c++20/format-inst.cc
>> >
>> >diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver
>> >index 2ff2aa02383..fe6e6bf15b4 100644
>> >--- a/libstdc++-v3/config/abi/pre/gnu.ver
>> >+++ b/libstdc++-v3/config/abi/pre/gnu.ver
>> >@@ -34,7 +34,9 @@ GLIBCXX_3.4 {
>> >       std::basic_[a-e]*;
>> >       std::basic_f[a-h]*;
>> > #     std::basic_filebuf;
>> >-      std::basic_f[j-r]*;
>> >+      std::basic_f[j-n]*;
>> >+#     std::basic_format_context;
>> >+      std::basic_f[p-r]*;
>> > #     std::basic_fstream;
>> >       std::basic_f[t-z]*;
>> >       std::basic_[g-h]*;
>> >@@ -2608,6 +2610,10 @@ GLIBCXX_3.4.35 {
>> >     _ZNSbIwSt11char_traitsIwESaIwEEC[12]EvQ26is_default_constructible_vIT1_E;
>> >     _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EEC[1-2]EvQ26is_default_constructible_vIT1_E;
>> >
>> >+    # __format::__do_vformat_to<char, 1>(_Sink_iter<char>, string_view, format_context&);
>> >+    # __format::__do_vformat_to<wchar_t, 1>(_Sink_iter<wchar_t>, wstring_view, format_context&);
>> >+    _ZNSt8__format15__do_vformat_toI[cw]Lj1EEENS_10_Sink_iterIT_EES3_St17basic_string_viewIS2_St11char_traitsIS2_EERSt20basic_format_contextIS3_S2_E;
>> >+
>> > #if defined (_WIN32) && !defined (__CYGWIN__)
>> >     _ZSt19__get_once_callablev;
>> >     _ZSt15__get_once_callv;
>> >diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
>> >index eca5bd213aa..57d2ddd511d 100644
>> >--- a/libstdc++-v3/include/std/format
>> >+++ b/libstdc++-v3/include/std/format
>> >@@ -5219,82 +5219,106 @@ namespace __format
>> > #endif
>> >     };
>> >
>> >-  template<typename _Out, typename _CharT, typename _Context>
>> >-    inline _Out
>> >-    __do_vformat_to(_Out __out, basic_string_view<_CharT> __fmt,
>> >-                  const basic_format_args<_Context>& __args,
>> >-                  const locale* __loc)
>> >+  template<typename _CharT, unsigned = __unicode::__literal_encoding_is_unicode<_CharT>()>
>> >+    _Sink_iter<_CharT>
>> >+    __do_vformat_to(_Sink_iter<_CharT> __out, basic_string_view<_CharT> __fmt,
>> >+                  __format_context<_CharT>& __ctx)
>> >     {
>> >-      if constexpr (is_same_v<_Out, _Sink_iter<_CharT>>)
>> >-      {
>> >-        if constexpr (is_same_v<_CharT, char>)
>> >-          // Fast path for "{}" format strings and simple format arg types.
>> >-          if (__fmt.size() == 2 && __fmt[0] == '{' && __fmt[1] == '}')
>> >-            {
>> >-              bool __done = false;
>> >-              __format::__visit_format_arg([&](auto& __arg) {
>> >-                using _Tp = remove_cvref_t<decltype(__arg)>;
>> >-                if constexpr (is_same_v<_Tp, bool>)
>> >+      if constexpr (is_same_v<_CharT, char>)
>> >+      // Fast path for "{}" format strings and simple format arg types.
>> >+      if (__fmt.size() == 2 && __fmt[0] == '{' && __fmt[1] == '}')
>> >+        {
>> >+          bool __done = false;
>> >+          __format::__visit_format_arg([&](auto& __arg) {
>> >+            using _Tp = remove_cvref_t<decltype(__arg)>;
>> >+            if constexpr (is_same_v<_Tp, bool>)
>> >+              {
>> >+                size_t __len = 4 + !__arg;
>> >+                const char* __chars[] = { "false", "true" };
>> >+                if (auto __res = __out._M_reserve(__len))
>> >                   {
>> >-                    size_t __len = 4 + !__arg;
>> >-                    const char* __chars[] = { "false", "true" };
>> >-                    if (auto __res = __out._M_reserve(__len))
>> >-                      {
>> >-                        __builtin_memcpy(__res.get(), __chars[__arg], __len);
>> >-                        __res._M_bump(__len);
>> >-                        __done = true;
>> >-                      }
>> >+                    __builtin_memcpy(__res.get(), __chars[__arg], __len);
>> >+                    __res._M_bump(__len);
>> >+                    __done = true;
>> >                   }
>> >-                else if constexpr (is_same_v<_Tp, char>)
>> >+              }
>> >+            else if constexpr (is_same_v<_Tp, char>)
>> >+              {
>> >+                if (auto __res = __out._M_reserve(1))
>> >                   {
>> >-                    if (auto __res = __out._M_reserve(1))
>> >-                      {
>> >-                        *__res.get() = __arg;
>> >-                        __res._M_bump(1);
>> >-                        __done = true;
>> >-                      }
>> >+                    *__res.get() = __arg;
>> >+                    __res._M_bump(1);
>> >+                    __done = true;
>> >                   }
>> >-                else if constexpr (is_integral_v<_Tp>)
>> >+              }
>> >+            else if constexpr (is_integral_v<_Tp>)
>> >+              {
>> >+                make_unsigned_t<_Tp> __uval;
>> >+                const bool __neg = __arg < 0;
>> >+                if (__neg)
>> >+                  __uval = make_unsigned_t<_Tp>(~__arg) + 1u;
>> >+                else
>> >+                  __uval = __arg;
>> >+                const auto __n = __detail::__to_chars_len(__uval);
>> >+                if (auto __res = __out._M_reserve(__n + __neg))
>> >                   {
>> >-                    make_unsigned_t<_Tp> __uval;
>> >-                    const bool __neg = __arg < 0;
>> >-                    if (__neg)
>> >-                      __uval = make_unsigned_t<_Tp>(~__arg) + 1u;
>> >-                    else
>> >-                      __uval = __arg;
>> >-                    const auto __n = __detail::__to_chars_len(__uval);
>> >-                    if (auto __res = __out._M_reserve(__n + __neg))
>> >-                      {
>> >-                        auto __ptr = __res.get();
>> >-                        *__ptr = '-';
>> >-                        __detail::__to_chars_10_impl(__ptr + (int)__neg, __n,
>> >-                                                     __uval);
>> >-                        __res._M_bump(__n + __neg);
>> >-                        __done = true;
>> >-                      }
>> >+                    auto __ptr = __res.get();
>> >+                    *__ptr = '-';
>> >+                    __detail::__to_chars_10_impl(__ptr + (int)__neg, __n,
>> >+                                                 __uval);
>> >+                    __res._M_bump(__n + __neg);
>> >+                    __done = true;
>> >                   }
>> >-                else if constexpr (is_convertible_v<_Tp, string_view>)
>> >+              }
>> >+            else if constexpr (is_convertible_v<_Tp, string_view>)
>> >+              {
>> >+                string_view __sv = __arg;
>> >+                if (auto __res = __out._M_reserve(__sv.size()))
>> >                   {
>> >-                    string_view __sv = __arg;
>> >-                    if (auto __res = __out._M_reserve(__sv.size()))
>> >-                      {
>> >-                        __builtin_memcpy(__res.get(), __sv.data(), __sv.size());
>> >-                        __res._M_bump(__sv.size());
>> >-                        __done = true;
>> >-                      }
>> >+                    __builtin_memcpy(__res.get(), __sv.data(), __sv.size());
>> >+                    __res._M_bump(__sv.size());
>> >+                    __done = true;
>> >                   }
>> >-              }, __args.get(0));
>> >+              }
>> >+          }, __ctx.arg(0));
>> >
>> >-              if (__done)
>> >-                return __out;
>> >-            }
>> >+          if (__done)
>> >+            return __out;
>> >+        }
>> >+
>> >+      _Formatting_scanner<_Sink_iter<_CharT>, _CharT> __scanner(__ctx, __fmt);
>> >+      __scanner._M_scan();
>> >+      return __out;
>> >+    }
>> >+
>> >+// The behavior of the formatters (interpretation of fill character) depends
>> >+// on the literal encoding. As explicit instantiation of __do_vformat_to
>> >+// instantiates formatters for types stored in basic_format_arg, we can
>> >+// support only single encoding, in this case unicode. This should cover
>> >+// most common use cases.
>> >+#if __cplusplus <= 202002L && _GLIBCXX_EXTERN_TEMPLATE > 0
>> >+    extern template _Sink_iter<char>
>> >+      __do_vformat_to<char, 1>(_Sink_iter<char>, string_view,
>> >+                             format_context&);
>> >+# ifdef _GLIBCXX_USE_WCHAR_T
>> >+    extern template _Sink_iter<wchar_t>
>> >+      __do_vformat_to<wchar_t, 1>(_Sink_iter<wchar_t>, wstring_view,
>> >+                                wformat_context&);
>> >+# endif
>> >+#endif
>> >
>> >+  template<typename _Out, typename _CharT, typename _Context>
>> >+    inline _Out
>> >+    __do_vformat_to(_Out __out, basic_string_view<_CharT> __fmt,
>> >+                  const basic_format_args<_Context>& __args,
>> >+                  const locale* __loc)
>> >+    {
>> >+      if constexpr (is_same_v<_Out, _Sink_iter<_CharT>>)
>> >+      {
>> >         auto __ctx = __loc == nullptr
>> >-                       ? _Context(__args, __out)
>> >-                       : _Context(__args, __out, *__loc);
>> >-        _Formatting_scanner<_Sink_iter<_CharT>, _CharT> __scanner(__ctx, __fmt);
>> >-        __scanner._M_scan();
>> >-        return __out;
>> >+                   ? _Context(__args, __out)
>> >+                   : _Context(__args, __out, *__loc);
>> >+        return __do_vformat_to(std::move(__out), __fmt, __ctx);
>> >       }
>> >       else if constexpr (__contiguous_char_iter<_CharT, _Out>)
>> >       {
>> >@@ -5311,7 +5335,7 @@ namespace __format
>> >     }
>> >
>> >   template<typename _Out, typename _CharT>
>> >-    format_to_n_result<_Out>
>> >+    inline format_to_n_result<_Out>
>> >     __do_vformat_to_n(_Out __out, iter_difference_t<_Out> __n,
>> >                     basic_string_view<_CharT> __fmt,
>> >                     const type_identity_t<
>> >diff --git a/libstdc++-v3/src/c++20/Makefile.am b/libstdc++-v3/src/c++20/Makefile.am
>> >index af5d2fa4057..c2311696415 100644
>> >--- a/libstdc++-v3/src/c++20/Makefile.am
>> >+++ b/libstdc++-v3/src/c++20/Makefile.am
>> >@@ -33,11 +33,11 @@ else
>> > extra_string_inst_sources =
>> > endif
>> >
>> >-
>> > if ENABLE_EXTERN_TEMPLATE
>> > # XTEMPLATE_FLAGS = -fno-implicit-templates
>> > inst_sources = \
>> >       sstream-inst.cc \
>> >+      format-inst.cc
>>
>> This is missing the backslash, so the next two lines are just orphaned
>> and not part of $(inst_sources) in the generated Makefile.in
>>
>> I've been trying to understand how this still works in your testing
>> ... I would expect all the C++20 exports for std::string to be
>> missing. I guess you already have string-inst.o and cow-string-inst.o
>> compiled, and maybe they're already in the libstdc++20convenience.a
>> archive? Dunno ... but the backslash above is needed.
>
> I have the slash locally here:
>  if ENABLE_EXTERN_TEMPLATE
>  # XTEMPLATE_FLAGS = -fno-implicit-templates
>  inst_sources = \
>         sstream-inst.cc \
> +       format-inst.cc \
>         string-inst.cc \
>         $(extra_string_inst_sources)
> I must have fixed that, and forgotten to amend the commit before sending
> the patch.

That explains it then!

The patch is OK for trunk with the typo and comment fixes, and the
regenerated Makefile.in for the backslash.

>>
>>
>> >       string-inst.cc \
>> >       $(extra_string_inst_sources)
>> > else
>> >@@ -73,6 +73,14 @@ format.lo: format.cc
>> > format.o: format.cc
>> >       $(CXXCOMPILE) -std=gnu++26 -fno-access-control -c $<
>> >
>> >+if ENABLE_EXTERN_TEMPLATE
>> >+# The unicode literal version of formatters is exported, override the encoding accordingly
>> >+format-inst.lo: format-inst.cc
>> >+      $(LTCXXCOMPILE) -fexec-charset=UTF-8 -c $<
>> >+format-inst.o: format-inst.cc
>> >+      $(CXXCOMPILE) -fexec-charset=UTF-8 -c $<
>> >+endif
>> >+
>> > if GLIBCXX_HOSTED
>> > libc__20convenience_la_SOURCES = $(sources)  $(inst_sources)
>> > else
>> >diff --git a/libstdc++-v3/src/c++20/Makefile.in b/libstdc++-v3/src/c++20/Makefile.in
>> >index a2386ec7ee0..44110878307 100644
>> >--- a/libstdc++-v3/src/c++20/Makefile.in
>> >+++ b/libstdc++-v3/src/c++20/Makefile.in
>> >@@ -124,11 +124,10 @@ CONFIG_CLEAN_VPATH_FILES =
>> > LTLIBRARIES = $(noinst_LTLIBRARIES)
>> > libc__20convenience_la_LIBADD =
>> > am__objects_1 = tzdb.lo format.lo atomic.lo clock.lo syncbuf.lo
>> >-@ENABLE_DUAL_ABI_TRUE@am__objects_2 = cow-string-inst.lo
>> >-@ENABLE_EXTERN_TEMPLATE_TRUE@am__objects_3 = sstream-inst.lo \
>> >-@ENABLE_EXTERN_TEMPLATE_TRUE@ string-inst.lo $(am__objects_2)
>> >+@ENABLE_EXTERN_TEMPLATE_TRUE@am__objects_2 = sstream-inst.lo \
>> >+@ENABLE_EXTERN_TEMPLATE_TRUE@ format-inst.lo
>> > @GLIBCXX_HOSTED_TRUE@am_libc__20convenience_la_OBJECTS =  \
>> >-@GLIBCXX_HOSTED_TRUE@ $(am__objects_1) $(am__objects_3)
>> >+@GLIBCXX_HOSTED_TRUE@ $(am__objects_1) $(am__objects_2)
>> > libc__20convenience_la_OBJECTS = $(am_libc__20convenience_la_OBJECTS)
>> > AM_V_lt = $(am__v_lt_@AM_V@)
>> > am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
>> >@@ -438,8 +437,7 @@ headers =
>> > # XTEMPLATE_FLAGS = -fno-implicit-templates
>> > @ENABLE_EXTERN_TEMPLATE_TRUE@inst_sources = \
>> > @ENABLE_EXTERN_TEMPLATE_TRUE@ sstream-inst.cc \
>> >-@ENABLE_EXTERN_TEMPLATE_TRUE@ string-inst.cc \
>> >-@ENABLE_EXTERN_TEMPLATE_TRUE@ $(extra_string_inst_sources)
>> >+@ENABLE_EXTERN_TEMPLATE_TRUE@ format-inst.cc
>> >
>> > sources = tzdb.cc format.cc atomic.cc clock.cc syncbuf.cc
>> > @GLIBCXX_HOSTED_FALSE@libc__20convenience_la_SOURCES =
>> >@@ -750,6 +748,8 @@ uninstall-am:
>> >
>> > .PRECIOUS: Makefile
>> >
>> >+@ENABLE_EXTERN_TEMPLATE_TRUE@ string-inst.cc \
>> >+@ENABLE_EXTERN_TEMPLATE_TRUE@ $(extra_string_inst_sources)
>> >
>> > vpath % $(top_srcdir)/src/c++20
>> >
>> >@@ -773,6 +773,12 @@ format.lo: format.cc
>> > format.o: format.cc
>> >       $(CXXCOMPILE) -std=gnu++26 -fno-access-control -c $<
>> >
>> >+# The unicode literal version of formatters is exported, override the encoding accordingly
>> >+@ENABLE_EXTERN_TEMPLATE_TRUE@format-inst.lo: format-inst.cc
>> >+@ENABLE_EXTERN_TEMPLATE_TRUE@ $(LTCXXCOMPILE) -fexec-charset=UTF-8 -c $<
>> >+@ENABLE_EXTERN_TEMPLATE_TRUE@format-inst.o: format-inst.cc
>> >+@ENABLE_EXTERN_TEMPLATE_TRUE@ $(CXXCOMPILE) -fexec-charset=UTF-8 -c $<
>> >+
>> > # Tell versions [3.59,3.63) of GNU make to not export all variables.
>> > # Otherwise a system limit (for SysV at least) may be exceeded.
>> > .NOEXPORT:
>> >diff --git a/libstdc++-v3/src/c++20/format-inst.cc b/libstdc++-v3/src/c++20/format-inst.cc
>> >new file mode 100644
>> >index 00000000000..1c5973b4bcb
>> >--- /dev/null
>> >+++ b/libstdc++-v3/src/c++20/format-inst.cc
>> >@@ -0,0 +1,46 @@
>> >+// Definitions for <chrono> formatting -*- C++ -*-
>>
>> s/<chrono> formatting/<format>/
>>
>> >+
>> >+// 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/>.
>> >+
>> >+#include <format>
>> >+#include <chrono>
>> >+
>> >+namespace std
>> >+{
>> >+_GLIBCXX_BEGIN_NAMESPACE_VERSION
>> >+namespace __format
>> >+{
>> >+
>> >+ template _Sink_iter<char>
>> >+   __do_vformat_to<char, 1>(_Sink_iter<char>, string_view,
>> >+                          format_context&);
>> >+
>> >+# ifdef _GLIBCXX_USE_WCHAR_T
>> >+ template _Sink_iter<wchar_t>
>> >+   __do_vformat_to<wchar_t, 1>(_Sink_iter<wchar_t>, wstring_view,
>> >+                             wformat_context&);
>> >+# endif
>> >+
>> >+} // namespace __format
>> >+_GLIBCXX_END_NAMESPACE_VERSION
>> >+} // namespace std
>> >--
>> >2.53.0
>> >
>> >
>>



More information about the Libstdc++ mailing list