[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