[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