[PATCH v2] libstdc++: Export explicit instantiations of __format::__do_vformat_to.
Jonathan Wakely
jwakely@redhat.com
Thu Apr 16 09:34:38 GMT 2026
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?
>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.
> 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