Hi, Jonathan Wakely <jwak...@redhat.com> writes:
> I am undecided about pushing this PATCH 2/2 to trunk. PATCH 1/2 needs to > be done now due to the ABI impact on cris-elf. This one could wait for > stage 1. (HP, this one probably isn't of interest to you, but I don't > know how to tell git-send-email to only CC you on the first patch). (haven't read the patchset, just weighing in with Git tips) You can, instead of specifying git send-email --cc=..., just place Cc: <whoever> in the patch file to Cc just that patch. G-S-E will aggregate them together before shipping out. Hope that helps, have a lovely day. > I'd like to do this change now so people can experiment with the new > feature by linking with -lstdc++exp, but the problem is that linking to > that library becomes a hard requirement for any uses of std::chrono > types with std::format in C++23. So code that compiles and links in > C++20 mode will fail to link in C++23 mode unless you use the extra > libstdc++exp.a library. The code might have worked fine in C++20 mode, > or it might have just linked OK but produce mojibake at runtime, it > depends on the locale. > > Declaring the new functions as weak and then checking them for non-null > at runtime doesn't really help, because the non-weak symbols in the > static lib won't be used without -Wl,--whole-archive -lstdc++exp because > it's OK for weak symbols to be unresolved. We could play games with > linker scripts so that libstdc++exp.a is a linker script which ensures > the non-weak defs get used, but I'm not convinced it's worth the hassle > for a feature that will probably get added to the main libstdc++.so in > stage 1 anyway. > > What do others think? Should I just push it and require -lstdc++exp for > chrono formatting in C++23 mode? > > -- >8 -- > > This implements the C++23 paper P2419R2 (Clarify handling of encodings > in localized formatting of chrono types). The requirement is that when > the literal encoding is "a Unicode encoding form" and the formatting > locale uses a different encoding, any locale-specific strings such as > "août" for std::chrono::August should be converted to the literal > encoding. > > Using the recently-added std::locale::encoding() function we can check > the locale's encoding and then use iconv if a conversion is needed. This > requires a new non-inline function in the library. As this is currently > experimental, the new symbol is added to libstdc++exp.a for now. This > means that formatting std::chrono types requires -lstdc++exp in C++23 > and later, at least until the new function is considered stable enough > to add to the main library. When that happens, we might want to consider > enabling these encoding conversions for C++20 too, treating it as a DR > that resolves LWG 3656. > > Because nl_langinfo_l and iconv_open both allocate memory, a naive > implementation would perform multiple allocations and deallocations for > every snippet of locale-specific text that needs to be converted to > UTF-8. To avoid that, a new internal locale::facet is defined to store > the text_encoding and an iconv_t descriptor, which are then cached in > the formatting locale. This requires access to the internals of a > std::locale object in src/c++26/text_encoding.cc, so that file now needs > to be compiled with -fno-access-control. When the new symbols move into > libstdc++.so we should be able to avoid that by adding new member > functions to std::locale. > > With this change we can increase the value of the __cpp_lib_format macro > for C++23. The value should be 202207 for P2419R2, but we already > implement P2510R3 (Formatting pointers) so can use the value 202304. > > libstdc++-v3/ChangeLog: > > PR libstdc++/109162 > * include/bits/chrono_io.h (_ChronoSpec::_M_locale_specific): > Add new accessor functions to use a reserved bit in _Spec. > (__formatter_chrono::_M_parse): Use _M_locale_specific(true) > when chrono-specs contains locale-dependent conversion > specifiers. > (__formatter_chrono::_M_format): Open iconv descriptor if > conversion to UTF-8 will be needed. > (__formatter_chrono::_M_write): New function to write a > localized string with possible character conversion. > (__formatter_chrono::_M_a_A, __formatter_chrono::_M_b_B) > (__formatter_chrono::_M_p, __formatter_chrono::_M_r) > (__formatter_chrono::_M_x, __formatter_chrono::_M_X) > (__formatter_chrono::_M_locale_fmt): Use _M_write. > * include/bits/version.def: Add C++23 values for format. > * include/bits/version.h: Regenerate. > * include/std/format (_GLIBCXX_P2518R3): Check feature test > macro instead of __cplusplus. > (basic_format_context): Declare __formatter_chrono as friend. > * src/c++26/Makefile.am: Use -fno-access-control for > text_encoding.o. > * src/c++26/Makefile.in: Regenerate. > * src/c++26/text_encoding.cc (__encoding): New locale facet. > (locale::encoding): Check for __encoding facet first. Add fast > path for "C" locale. > (__with_encoding_conversion): New function to add __encoding > facet to a locale. > (__locale_encoding_to_utf8): New function to convert a string > from a locale's encoding to UTF-8. > * testsuite/20_util/duration/io.cc: Add -lstdc++exp for C++23. > * testsuite/std/time/clock/file/io.cc: Likewise. > * testsuite/std/time/clock/gps/io.cc: Likewise. > * testsuite/std/time/clock/local/io.cc: Likewise. > * testsuite/std/time/clock/system/io.cc: Likewise. > * testsuite/std/time/clock/tai/io.cc: Likewise. > * testsuite/std/time/clock/utc/io.cc: Likewise. > * testsuite/std/time/day/io.cc: Likewise. > * testsuite/std/time/exceptions.cc: Likewise. > * testsuite/std/time/format.cc: Likewise. > * testsuite/std/time/hh_mm_ss/io.cc: Likewise. > * testsuite/std/time/month/io.cc: Likewise. > * testsuite/std/time/month_day/io.cc: Likewise. > * testsuite/std/time/month_day_last/io.cc: Likewise. > * testsuite/std/time/month_weekday/io.cc: Likewise. > * testsuite/std/time/month_weekday_last/io.cc: Likewise. > * testsuite/std/time/time_zone/get_info_local.cc: Likewise. > * testsuite/std/time/weekday/io.cc: Likewise. > * testsuite/std/time/weekday_indexed/io.cc: Likewise. > * testsuite/std/time/weekday_last/io.cc: Likewise. > * testsuite/std/time/year/io.cc: Likewise. > * testsuite/std/time/year_month/io.cc: Likewise. > * testsuite/std/time/year_month_day/io.cc: Likewise. > * testsuite/std/time/year_month_day_last/io.cc: Likewise. > * testsuite/std/time/year_month_weekday/io.cc: Likewise. > * testsuite/std/time/year_month_weekday_last/io.cc: Likewise. > * testsuite/std/time/zoned_time/1.cc: Likewise. > * testsuite/std/time/zoned_time/io.cc: Likewise. > * testsuite/std/time/format_localized.cc: New test. > --- > libstdc++-v3/include/bits/chrono_io.h | 103 ++++++++++-- > libstdc++-v3/include/bits/version.def | 34 +++- > libstdc++-v3/include/bits/version.h | 7 +- > libstdc++-v3/include/std/format | 16 +- > libstdc++-v3/src/c++26/Makefile.am | 6 + > libstdc++-v3/src/c++26/Makefile.in | 6 + > libstdc++-v3/src/c++26/text_encoding.cc | 151 +++++++++++++++++- > libstdc++-v3/testsuite/20_util/duration/io.cc | 1 + > .../testsuite/std/time/clock/file/io.cc | 1 + > .../testsuite/std/time/clock/gps/io.cc | 1 + > .../testsuite/std/time/clock/local/io.cc | 1 + > .../testsuite/std/time/clock/system/io.cc | 1 + > .../testsuite/std/time/clock/tai/io.cc | 1 + > .../testsuite/std/time/clock/utc/io.cc | 1 + > libstdc++-v3/testsuite/std/time/day/io.cc | 1 + > libstdc++-v3/testsuite/std/time/exceptions.cc | 1 + > libstdc++-v3/testsuite/std/time/format.cc | 1 + > .../testsuite/std/time/format_localized.cc | 48 ++++++ > .../testsuite/std/time/hh_mm_ss/io.cc | 1 + > libstdc++-v3/testsuite/std/time/month/io.cc | 1 + > .../testsuite/std/time/month_day/io.cc | 1 + > .../testsuite/std/time/month_day_last/io.cc | 1 + > .../testsuite/std/time/month_weekday/io.cc | 1 + > .../std/time/month_weekday_last/io.cc | 1 + > .../std/time/time_zone/get_info_local.cc | 1 + > libstdc++-v3/testsuite/std/time/weekday/io.cc | 1 + > .../testsuite/std/time/weekday_indexed/io.cc | 1 + > .../testsuite/std/time/weekday_last/io.cc | 1 + > libstdc++-v3/testsuite/std/time/year/io.cc | 1 + > .../testsuite/std/time/year_month/io.cc | 1 + > .../testsuite/std/time/year_month_day/io.cc | 1 + > .../std/time/year_month_day_last/io.cc | 1 + > .../std/time/year_month_weekday/io.cc | 1 + > .../std/time/year_month_weekday_last/io.cc | 1 + > .../testsuite/std/time/zoned_time/1.cc | 1 + > .../testsuite/std/time/zoned_time/io.cc | 1 + > 36 files changed, 375 insertions(+), 24 deletions(-) > create mode 100644 libstdc++-v3/testsuite/std/time/format_localized.cc > > diff --git a/libstdc++-v3/include/bits/chrono_io.h > b/libstdc++-v3/include/bits/chrono_io.h > index 82f2d39ec44..9f5b9e13a82 100644 > --- a/libstdc++-v3/include/bits/chrono_io.h > +++ b/libstdc++-v3/include/bits/chrono_io.h > @@ -210,6 +210,20 @@ namespace __format > struct _ChronoSpec : _Spec<_CharT> > { > basic_string_view<_CharT> _M_chrono_specs; > + > + // Use one of the reserved bits in __format::_Spec<C>. > + // This indicates that a locale-dependent conversion specifier such as > + // %a is used in the chrono-specs. This is not the same as the > + // _Spec<C>::_M_localized member which indicates that "L" was present > + // in the format-spec, e.g. "{:L%a}" is localized and locale-specific, > + // but "{:L}" is only localized and "{:%a}" is only locale-specific. > + constexpr bool > + _M_locale_specific() const noexcept > + { return this->_M_reserved & 1; } > + > + constexpr void > + _M_locale_specific(bool __b) noexcept > + { this->_M_reserved |= __b; } > }; > > // Represents the information provided by a chrono type. > @@ -304,11 +318,12 @@ namespace __format > const auto __chrono_specs = __first++; // Skip leading '%' > if (*__chrono_specs != '%') > __throw_format_error("chrono format error: no '%' at start of " > - "chrono-specs"); > + "chrono-specs"); > > _CharT __mod{}; > bool __conv = true; > int __needed = 0; > + bool __locale_specific = false; > > while (__first != __last) > { > @@ -321,15 +336,18 @@ namespace __format > case 'a': > case 'A': > __needed = _Weekday; > + __locale_specific = true; > break; > case 'b': > case 'h': > case 'B': > __needed = _Month; > + __locale_specific = true; > break; > case 'c': > __needed = _DateTime; > __allowed_mods = _Mod_E; > + __locale_specific = true; > break; > case 'C': > __needed = _Year; > @@ -367,6 +385,8 @@ namespace __format > break; > case 'p': > case 'r': > + __locale_specific = true; > + [[fallthrough]]; > case 'R': > case 'T': > __needed = _TimeOfDay; > @@ -392,10 +412,12 @@ namespace __format > break; > case 'x': > __needed = _Date; > + __locale_specific = true; > __allowed_mods = _Mod_E; > break; > case 'X': > __needed = _TimeOfDay; > + __locale_specific = true; > __allowed_mods = _Mod_E; > break; > case 'y': > @@ -435,6 +457,8 @@ namespace __format > || (__mod == 'O' && !(__allowed_mods & _Mod_O))) > __throw_format_error("chrono format error: invalid " > " modifier in chrono-specs"); > + if (__mod && __c != 'z') > + __locale_specific = true; > __mod = _CharT(); > > if ((__parts & __needed) != __needed) > @@ -466,6 +490,7 @@ namespace __format > _M_spec = __spec; > _M_spec._M_chrono_specs > = __string_view(__chrono_specs, __first - __chrono_specs); > + _M_spec._M_locale_specific(__locale_specific); > > return __first; > } > @@ -485,6 +510,26 @@ namespace __format > if (__first == __last) > return _M_format_to_ostream(__t, __fc, __is_neg); > > +#if __glibcxx_format >= 202207L // C++ >= 23 > +#if _GLIBCXX_USE_CXX11_ABI > + // _GLIBCXX_RESOLVE_LIB_DEFECTS > + // 3565. Handling of encodings in localized formatting > + // of chrono types is underspecified > + if constexpr (is_same_v<_CharT, char>) > + if constexpr (__unicode::__literal_encoding_is_utf8()) > + if (_M_spec._M_localized && _M_spec._M_locale_specific()) > + { > + extern locale __with_encoding_conversion(const locale&); > + > + // Allocate and cache the necessary state to convert strings > + // in the locale's encoding to UTF-8. > + locale __loc = __fc.locale(); > + if (__loc != locale::classic()) > + __fc._M_loc = __with_encoding_conversion(__loc); > + } > +#endif > +#endif > + > _Sink_iter<_CharT> __out; > __format::_Str_sink<_CharT> __sink; > bool __write_direct = false; > @@ -741,6 +786,37 @@ namespace __format > static constexpr _CharT _S_space = _S_chars[14]; > static constexpr const _CharT* _S_empty_spec = _S_chars + 15; > > + template<typename _OutIter> > + _OutIter > + _M_write(_OutIter __out, const locale& __loc, __string_view __s) const > + { > + // FIXME: It would be nice to treat this as a DR for C++20 > + // but it currently requires new symbols in libstdc++exp.a > + // which we don't want to require for C++20. Could do it when > + // those symbols move into the non-experimental library. > +#if __glibcxx_format >= 202207L // C++ >= 23 > +#if _GLIBCXX_USE_CXX11_ABI > + // _GLIBCXX_RESOLVE_LIB_DEFECTS > + // 3565. Handling of encodings in localized formatting > + // of chrono types is underspecified > + using _StringType = string; > + string __buf; > + if constexpr (is_same_v<_CharT, char>) > + if constexpr (__unicode::__literal_encoding_is_utf8()) > + if (_M_spec._M_localized && _M_spec._M_locale_specific() > + && __loc != locale::classic()) > + { > + extern string_view > + __locale_encoding_to_utf8(const std::locale&, string_view, > + _StringType&); > + > + __s = __locale_encoding_to_utf8(__loc, __s, __buf); > + } > +#endif > +#endif > + return __format::__write(std::move(__out), __s); > + } > + > template<typename _Tp, typename _FormatContext> > typename _FormatContext::iterator > _M_a_A(const _Tp& __t, typename _FormatContext::iterator __out, > @@ -760,7 +836,7 @@ namespace __format > else > __tp._M_days_abbreviated(__days); > __string_view __str(__days[__wd.c_encoding()]); > - return __format::__write(std::move(__out), __str); > + return _M_write(std::move(__out), __loc, __str); > } > > template<typename _Tp, typename _FormatContext> > @@ -781,7 +857,7 @@ namespace __format > else > __tp._M_months_abbreviated(__months); > __string_view __str(__months[(unsigned)__m - 1]); > - return __format::__write(std::move(__out), __str); > + return _M_write(std::move(__out), __loc, __str); > } > > template<typename _Tp, typename _FormatContext> > @@ -1058,8 +1134,8 @@ namespace __format > const auto& __tp = use_facet<__timepunct<_CharT>>(__loc); > const _CharT* __ampm[2]; > __tp._M_am_pm(__ampm); > - return std::format_to(std::move(__out), _S_empty_spec, > - __ampm[__hms.hours().count() >= 12]); > + return _M_write(std::move(__out), __loc, > + __ampm[__hms.hours().count() >= 12]); > } > > template<typename _Tp, typename _FormatContext> > @@ -1094,8 +1170,9 @@ namespace __format > basic_string<_CharT> __fmt(_S_empty_spec); > __fmt.insert(1u, 1u, _S_colon); > __fmt.insert(2u, __ampm_fmt); > - return std::vformat_to(std::move(__out), __fmt, > - std::make_format_args<_FormatContext>(__t)); > + using _FmtStr = _Runtime_format_string<_CharT>; > + return _M_write(std::move(__out), __loc, > + std::format(__loc, _FmtStr(__fmt), __t)); > } > > template<typename _Tp, typename _FormatContext> > @@ -1278,8 +1355,9 @@ namespace __format > basic_string<_CharT> __fmt(_S_empty_spec); > __fmt.insert(1u, 1u, _S_colon); > __fmt.insert(2u, __rep); > - return std::vformat_to(std::move(__out), __fmt, > - std::make_format_args<_FormatContext>(__t)); > + using _FmtStr = _Runtime_format_string<_CharT>; > + return _M_write(std::move(__out), __loc, > + std::format(__loc, _FmtStr(__fmt), __t)); > } > > template<typename _Tp, typename _FormatContext> > @@ -1301,8 +1379,9 @@ namespace __format > basic_string<_CharT> __fmt(_S_empty_spec); > __fmt.insert(1u, 1u, _S_colon); > __fmt.insert(2u, __rep); > - return std::vformat_to(std::move(__out), __fmt, > - std::make_format_args<_FormatContext>(__t)); > + using _FmtStr = _Runtime_format_string<_CharT>; > + return _M_write(std::move(__out), __loc, > + std::format(__loc, _FmtStr(__fmt), __t)); > } > > template<typename _Tp, typename _FormatContext> > @@ -1579,7 +1658,7 @@ namespace __format > const auto& __tp = use_facet<time_put<_CharT>>(__loc); > __tp.put(__os, __os, _S_space, &__tm, __fmt, __mod); > if (__os) > - __out = __format::__write(std::move(__out), __os.view()); > + __out = _M_write(std::move(__out), __loc, __os.view()); > return __out; > } > }; > diff --git a/libstdc++-v3/include/bits/version.def > b/libstdc++-v3/include/bits/version.def > index 8fb8a2877ee..29ca973fe43 100644 > --- a/libstdc++-v3/include/bits/version.def > +++ b/libstdc++-v3/include/bits/version.def > @@ -1160,14 +1160,25 @@ ftms = { > }; > > ftms = { > + name = format; > + // 202311 P2918R2 Runtime format strings II > + // values = { > + // v = 202311; > + // cxxmin = 26; > + // hosted = yes; > + // }; > + // 202207 Encodings in localized formatting of chrono, basic-format-string. > + // 202304 P2510R3 Formatting pointers > + // 202305 P2757R3 Type checking format args > + // 202306 P2637R3 Member visit > + values = { > + v = 202304; > + cxxmin = 23; > + hosted = yes; > + }; > // 201907 Text Formatting, Integration of chrono, printf corner cases. > // 202106 std::format improvements. > // 202110 Fixing locale handling in chrono formatters, generator-like > types. > - // 202207 Encodings in localized formatting of chrono, basic-format-string. > - // 202207 P2286R8 Formatting Ranges > - // 202207 P2585R1 Improving default container formatting > - // TODO: #define __cpp_lib_format_ranges 202207L > - name = format; > values = { > v = 202110; > cxxmin = 20; > @@ -1364,6 +1375,19 @@ ftms = { > }; > }; > > +// ftms = { > + // name = format_ranges; > + // 202207 P2286R8 Formatting Ranges > + // 202207 P2585R1 Improving default container formatting > + // LWG3750 Too many papers bump __cpp_lib_format > + // TODO: #define __cpp_lib_format_ranges 202207L > + // values = { > + // v = 202207; > + // cxxmin = 23; > + // hosted = yes; > + // }; > +// }; > + > ftms = { > name = freestanding_algorithm; > values = { > diff --git a/libstdc++-v3/include/bits/version.h > b/libstdc++-v3/include/bits/version.h > index fa4e89cf845..714bf064950 100644 > --- a/libstdc++-v3/include/bits/version.h > +++ b/libstdc++-v3/include/bits/version.h > @@ -1304,7 +1304,12 @@ > #undef __glibcxx_want_barrier > > #if !defined(__cpp_lib_format) > -# if (__cplusplus >= 202002L) && _GLIBCXX_HOSTED > +# if (__cplusplus >= 202100L) && _GLIBCXX_HOSTED > +# define __glibcxx_format 202304L > +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_format) > +# define __cpp_lib_format 202304L > +# endif > +# elif (__cplusplus >= 202002L) && _GLIBCXX_HOSTED > # define __glibcxx_format 202110L > # if defined(__glibcxx_want_all) || defined(__glibcxx_want_format) > # define __cpp_lib_format 202110L > diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format > index 6c958bc11a5..1f72cb9b9ff 100644 > --- a/libstdc++-v3/include/std/format > +++ b/libstdc++-v3/include/std/format > @@ -2341,10 +2341,10 @@ namespace __format > > // _GLIBCXX_RESOLVE_LIB_DEFECTS > // P2510R3 Formatting pointers > -#if __cplusplus > 202302L || ! defined __STRICT_ANSI__ > -#define _GLIBCXX_P2518R3 1 > +#if __glibcxx_format >= 202304L || ! defined __STRICT_ANSI__ > +# define _GLIBCXX_P2518R3 1 > #else > -#define _GLIBCXX_P2518R3 0 > +# define _GLIBCXX_P2518R3 0 > #endif > > #if _GLIBCXX_P2518R3 > @@ -3788,6 +3788,9 @@ namespace __format > __do_vformat_to(_Out, basic_string_view<_CharT>, > const basic_format_args<_Context>&, > const locale* = nullptr); > + > + template<typename _CharT> struct __formatter_chrono; > + > } // namespace __format > /// @endcond > > @@ -3798,6 +3801,11 @@ namespace __format > * this class template explicitly. For typical uses of `std::format` the > * library will use the specializations `std::format_context` (for `char`) > * and `std::wformat_context` (for `wchar_t`). > + * > + * You are not allowed to define partial or explicit specializations of > + * this class template. > + * > + * @since C++20 > */ > template<typename _Out, typename _CharT> > class basic_format_context > @@ -3824,6 +3832,8 @@ namespace __format > const basic_format_args<_Context2>&, > const locale*); > > + friend __format::__formatter_chrono<_CharT>; > + > public: > basic_format_context() = default; > ~basic_format_context() = default; > diff --git a/libstdc++-v3/src/c++26/Makefile.am > b/libstdc++-v3/src/c++26/Makefile.am > index 000ced1f501..1f9dff7e737 100644 > --- a/libstdc++-v3/src/c++26/Makefile.am > +++ b/libstdc++-v3/src/c++26/Makefile.am > @@ -46,6 +46,12 @@ else > libc__26convenience_la_SOURCES = > endif > > +# This needs access to the internals of std::locale. > +text_encoding.lo: text_encoding.cc > + $(LTCXXCOMPILE) -fno-access-control -c $< > +text_encoding.o: text_encoding.cc > + $(CXXCOMPILE) -fno-access-control -c $< > + > # AM_CXXFLAGS needs to be in each subdirectory so that it can be > # modified in a per-library or per-sub-library way. Need to manually > # set this option because CONFIG_CXXFLAGS has to be after > diff --git a/libstdc++-v3/src/c++26/Makefile.in > b/libstdc++-v3/src/c++26/Makefile.in > index 77e73b2b265..96947ccdd0c 100644 > --- a/libstdc++-v3/src/c++26/Makefile.in > +++ b/libstdc++-v3/src/c++26/Makefile.in > @@ -742,6 +742,12 @@ uninstall-am: > > vpath % $(top_srcdir)/src/c++26 > > +# This needs access to the internals of std::locale. > +text_encoding.lo: text_encoding.cc > + $(LTCXXCOMPILE) -fno-access-control -c $< > +text_encoding.o: text_encoding.cc > + $(CXXCOMPILE) -fno-access-control -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++26/text_encoding.cc > b/libstdc++-v3/src/c++26/text_encoding.cc > index b9a50ef1a00..fc5141e02c7 100644 > --- a/libstdc++-v3/src/c++26/text_encoding.cc > +++ b/libstdc++-v3/src/c++26/text_encoding.cc > @@ -26,17 +26,28 @@ > #include <locale> > > #ifdef _GLIBCXX_USE_NL_LANGINFO_L > +#include <memory> // make_unique > +#include <string.h> // strlen, strcpy > #include <locale.h> > #if __has_include(<xlocale.h>) > # include <xlocale.h> > #endif > #include <langinfo.h> > > +#ifdef _GLIBCXX_HAVE_ICONV > +# include <format> > +# include <chrono> > +# include <iconv.h> > +# include <errno.h> > +#endif > + > #if __CHAR_BIT__ == 8 > namespace std > { > _GLIBCXX_BEGIN_NAMESPACE_VERSION > - > +namespace > +{ > +// Attempt to determine the text_encoding used by the named locale. > text_encoding > __locale_encoding(const char* name) > { > @@ -54,6 +65,48 @@ __locale_encoding(const char* name) > return enc; > } > > +// A non-standard locale::facet that caches the locale's std::text_encoding > +// and an iconv descriptor for converting from that encoding to UTF-8. > +struct __encoding : locale::facet > +{ > + static locale::id id; > + > + explicit > + __encoding(const text_encoding& enc, size_t refs = 0) > + : facet(refs), _M_enc(enc) > + { > +#if defined _GLIBCXX_HAVE_ICONV > + if (enc != text_encoding::UTF8 && enc != text_encoding::ASCII) > + _M_cd = ::iconv_open("UTF-8", enc.name()); > +#endif > + } > + > + ~__encoding() > + { > +#if defined _GLIBCXX_HAVE_ICONV > + if (_M_has_desc()) > + ::iconv_close(_M_cd); > +#endif > + } > + > + bool _M_has_desc() const > + { > +#if defined _GLIBCXX_HAVE_ICONV > + return _M_cd != (::iconv_t)-1; > +#else > + return false; > +#endif > + } > + > + text_encoding _M_enc; > +#if defined _GLIBCXX_HAVE_ICONV > + ::iconv_t _M_cd = (::iconv_t)-1; > +#endif > +}; > + > +locale::id __encoding::id; > + > +} // namespace > _GLIBCXX_END_NAMESPACE_VERSION > } // namespace std > > @@ -87,8 +140,100 @@ std::text_encoding::_M_is_environment() const > std::text_encoding > std::locale::encoding() const > { > - return std::__locale_encoding(name().c_str()); > + if (auto enc_facet = std::__try_use_facet<__encoding>(*this)) > + return enc_facet->_M_enc; > + string name = this->name(); > + if (name == "*") > + return {}; > + if (name == "C") > + return text_encoding(text_encoding::ASCII); > + return __locale_encoding(name.c_str()); > } > #endif // CHAR_BIT == 8 > - > #endif // _GLIBCXX_USE_NL_LANGINFO_L > + > +namespace std > +{ > +_GLIBCXX_BEGIN_NAMESPACE_VERSION > +namespace __format > +{ > +// Helpers for P2419R2 > +// (Clarify handling of encodings in localized formatting of chrono types) > +// Convert a string from the locale's charset to UTF-8. > + > +std::locale > +__with_encoding_conversion(const std::locale& loc) > +{ > +#if defined _GLIBCXX_USE_NL_LANGINFO_L && __CHAR_BIT__ == 8 > + const text_encoding locenc = loc.encoding(); > + > + if (locenc == text_encoding::UTF8 || locenc == text_encoding::ASCII > + || locenc == text_encoding::unknown) > + return loc; > + > + auto impl = std::make_unique<locale::_Impl>(*loc._M_impl, 1); > + auto facetp = std::make_unique<__encoding>(locenc); > + locale loc2(loc, facetp.get()); // FIXME: PR libstdc++/113704 > + facetp.release(); > + // FIXME: Ideally we wouldn't need to reallocate this string again, > + // just don't delete[] it in the locale(locale, Facet*) constructor. > + if (const char* name = loc._M_impl->_M_names[0]) > + { > + loc2._M_impl->_M_names[0] = new char[strlen(name) + 1]; > + strcpy(loc2._M_impl->_M_names[0], name); > + } > + return loc2; > +#else > + return loc; > +#endif > +} > + > +string_view > +__locale_encoding_to_utf8(const std::locale& loc, string_view str, > + string& outbuf) > +{ > +#ifdef _GLIBCXX_HAVE_ICONV > + // Don't need to use __try_use_facet with its dynamic_cast<__encoding*>, > + // since we know there are no types derived from __encoding. If the array > + // element is non-null, we have the facet. > + auto id = __encoding::id._M_id(); > + auto enc_facet = static_cast<const > __encoding*>(loc._M_impl->_M_facets[id]); > + if (!enc_facet || !enc_facet->_M_has_desc()) > + return str; > + > + size_t inbytesleft = str.size(); > + size_t written = 0; > + bool done = false; > + > + auto overwrite = [&](char* p, size_t n) { > + auto inbytes = const_cast<char*>(str.data()) + str.size() - inbytesleft; > + char* outbytes = p + written; > + size_t outbytesleft = n - written; > + size_t res = ::iconv(enc_facet->_M_cd, &inbytes, &inbytesleft, > + &outbytes, &outbytesleft); > + if (res == (size_t)-1) > + { > + if (errno != E2BIG) > + { > + done = true; > + return 0zu; > + } > + } > + else > + done = true; > + written = outbytes - p; > + return written; > + }; > + do > + outbuf.resize_and_overwrite(outbuf.capacity() + (inbytesleft * 3 / 2), > + overwrite); > + while (!done); > + if (outbuf.size()) > + str = outbuf; > +#endif // HAVE_ICONV > + > + return str; > +} > +} // namespace __format > +_GLIBCXX_END_NAMESPACE_VERSION > +} // namespace std > diff --git a/libstdc++-v3/testsuite/20_util/duration/io.cc > b/libstdc++-v3/testsuite/20_util/duration/io.cc > index e141baf42dc..30469fb861d 100644 > --- a/libstdc++-v3/testsuite/20_util/duration/io.cc > +++ b/libstdc++-v3/testsuite/20_util/duration/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-timeout-factor 2 } > > diff --git a/libstdc++-v3/testsuite/std/time/clock/file/io.cc > b/libstdc++-v3/testsuite/std/time/clock/file/io.cc > index 9ab9f10ec77..2ab4a43c7e3 100644 > --- a/libstdc++-v3/testsuite/std/time/clock/file/io.cc > +++ b/libstdc++-v3/testsuite/std/time/clock/file/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-timeout-factor 2 } > > diff --git a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc > b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc > index d5405f61520..c3db63ab524 100644 > --- a/libstdc++-v3/testsuite/std/time/clock/gps/io.cc > +++ b/libstdc++-v3/testsuite/std/time/clock/gps/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-timeout-factor 2 } > > diff --git a/libstdc++-v3/testsuite/std/time/clock/local/io.cc > b/libstdc++-v3/testsuite/std/time/clock/local/io.cc > index bb682cd40cf..bb0ba0df419 100644 > --- a/libstdc++-v3/testsuite/std/time/clock/local/io.cc > +++ b/libstdc++-v3/testsuite/std/time/clock/local/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-timeout-factor 2 } > > diff --git a/libstdc++-v3/testsuite/std/time/clock/system/io.cc > b/libstdc++-v3/testsuite/std/time/clock/system/io.cc > index 2cc116156f2..dcb28dd51cf 100644 > --- a/libstdc++-v3/testsuite/std/time/clock/system/io.cc > +++ b/libstdc++-v3/testsuite/std/time/clock/system/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-timeout-factor 2 } > > diff --git a/libstdc++-v3/testsuite/std/time/clock/tai/io.cc > b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc > index 0fd61c0e612..b85fb761d99 100644 > --- a/libstdc++-v3/testsuite/std/time/clock/tai/io.cc > +++ b/libstdc++-v3/testsuite/std/time/clock/tai/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-timeout-factor 2 } > > diff --git a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc > b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc > index 55c53dc4057..867c912cfdd 100644 > --- a/libstdc++-v3/testsuite/std/time/clock/utc/io.cc > +++ b/libstdc++-v3/testsuite/std/time/clock/utc/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-timeout-factor 2 } > > diff --git a/libstdc++-v3/testsuite/std/time/day/io.cc > b/libstdc++-v3/testsuite/std/time/day/io.cc > index 36ce7ec7d17..eb9be402e6f 100644 > --- a/libstdc++-v3/testsuite/std/time/day/io.cc > +++ b/libstdc++-v3/testsuite/std/time/day/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/exceptions.cc > b/libstdc++-v3/testsuite/std/time/exceptions.cc > index 06b8a53c353..1ecf502b62d 100644 > --- a/libstdc++-v3/testsuite/std/time/exceptions.cc > +++ b/libstdc++-v3/testsuite/std/time/exceptions.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-effective-target tzdb } > > diff --git a/libstdc++-v3/testsuite/std/time/format.cc > b/libstdc++-v3/testsuite/std/time/format.cc > index d6e35832cb5..18d2171c117 100644 > --- a/libstdc++-v3/testsuite/std/time/format.cc > +++ b/libstdc++-v3/testsuite/std/time/format.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-timeout-factor 2 } > > diff --git a/libstdc++-v3/testsuite/std/time/format_localized.cc > b/libstdc++-v3/testsuite/std/time/format_localized.cc > new file mode 100644 > index 00000000000..f1bfe94fddc > --- /dev/null > +++ b/libstdc++-v3/testsuite/std/time/format_localized.cc > @@ -0,0 +1,48 @@ > +// { dg-options "-fexec-charset=UTF-8 -lstdc++exp" } > +// { dg-do run { target c++23 } } > +// { dg-require-namedlocale "ru_UA.koi8u" } > +// { dg-require-namedlocale "es_ES.ISO8859-1" } > +// { dg-require-namedlocale "fr_FR.ISO8859-1" } > +// { dg-require-effective-target cxx11_abi } > + > +// P2419R2 > +// Clarify handling of encodings in localized formatting of chrono types > + > +// Localized date-time strings such as "février" should be converted to UTF-8 > +// if the locale uses a different encoding. > + > +#include <chrono> > +#include <format> > +#include <testsuite_hooks.h> > + > +void > +test_ru() > +{ > + std::locale loc("ru_UA.koi8u"); > + auto s = std::format(loc, "День недели: {:L}", std::chrono::Monday); > + VERIFY( s == "День недели: Пн" ); > +} > + > +void > +test_es() > +{ > + std::locale loc(ISO_8859(1,es_ES)); > + auto s = std::format(loc, "Día de la semana: {:L%A %a}", > std::chrono::Wednesday); > + VERIFY( s == "Día de la semana: miércoles mié" ); > +} > + > +void > +test_fr() > +{ > + std::locale loc(ISO_8859(1,fr_FR)); > + auto s = std::format(loc, "Six mois après {0:L%b}, c'est {1:L%B}.", > + std::chrono::February, std::chrono::August); > + VERIFY( s == "Six mois après févr., c'est août." ); > +} > + > +int main() > +{ > + test_ru(); > + test_es(); > + test_fr(); > +} > diff --git a/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc > b/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc > index d651dfbcdc8..551e4ed9a37 100644 > --- a/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc > +++ b/libstdc++-v3/testsuite/std/time/hh_mm_ss/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-timeout-factor 2 } > > diff --git a/libstdc++-v3/testsuite/std/time/month/io.cc > b/libstdc++-v3/testsuite/std/time/month/io.cc > index 99ec0737305..564eed54706 100644 > --- a/libstdc++-v3/testsuite/std/time/month/io.cc > +++ b/libstdc++-v3/testsuite/std/time/month/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/month_day/io.cc > b/libstdc++-v3/testsuite/std/time/month_day/io.cc > index 30aa5881356..7f34f027ba9 100644 > --- a/libstdc++-v3/testsuite/std/time/month_day/io.cc > +++ b/libstdc++-v3/testsuite/std/time/month_day/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/month_day_last/io.cc > b/libstdc++-v3/testsuite/std/time/month_day_last/io.cc > index c12cee848ed..94d54b9b124 100644 > --- a/libstdc++-v3/testsuite/std/time/month_day_last/io.cc > +++ b/libstdc++-v3/testsuite/std/time/month_day_last/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/month_weekday/io.cc > b/libstdc++-v3/testsuite/std/time/month_weekday/io.cc > index 82cac648905..0fee88bb3a3 100644 > --- a/libstdc++-v3/testsuite/std/time/month_weekday/io.cc > +++ b/libstdc++-v3/testsuite/std/time/month_weekday/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/month_weekday_last/io.cc > b/libstdc++-v3/testsuite/std/time/month_weekday_last/io.cc > index 47968d0e06d..e6a6751bb4a 100644 > --- a/libstdc++-v3/testsuite/std/time/month_weekday_last/io.cc > +++ b/libstdc++-v3/testsuite/std/time/month_weekday_last/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc > b/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc > index d2972aca7df..af830374700 100644 > --- a/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc > +++ b/libstdc++-v3/testsuite/std/time/time_zone/get_info_local.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-effective-target tzdb } > > diff --git a/libstdc++-v3/testsuite/std/time/weekday/io.cc > b/libstdc++-v3/testsuite/std/time/weekday/io.cc > index a56cdaef88c..f102c9250e9 100644 > --- a/libstdc++-v3/testsuite/std/time/weekday/io.cc > +++ b/libstdc++-v3/testsuite/std/time/weekday/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/weekday_indexed/io.cc > b/libstdc++-v3/testsuite/std/time/weekday_indexed/io.cc > index 29255ad5c06..42fc9b914a1 100644 > --- a/libstdc++-v3/testsuite/std/time/weekday_indexed/io.cc > +++ b/libstdc++-v3/testsuite/std/time/weekday_indexed/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/weekday_last/io.cc > b/libstdc++-v3/testsuite/std/time/weekday_last/io.cc > index 6f76922195d..d069d857ff0 100644 > --- a/libstdc++-v3/testsuite/std/time/weekday_last/io.cc > +++ b/libstdc++-v3/testsuite/std/time/weekday_last/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/year/io.cc > b/libstdc++-v3/testsuite/std/time/year/io.cc > index bcaa57faeb7..1bdc95ddbd1 100644 > --- a/libstdc++-v3/testsuite/std/time/year/io.cc > +++ b/libstdc++-v3/testsuite/std/time/year/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/year_month/io.cc > b/libstdc++-v3/testsuite/std/time/year_month/io.cc > index 7bb3442e299..e7963d31ae6 100644 > --- a/libstdc++-v3/testsuite/std/time/year_month/io.cc > +++ b/libstdc++-v3/testsuite/std/time/year_month/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc > b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc > index cb82ef3b612..e7dc60b9dea 100644 > --- a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc > +++ b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/year_month_day_last/io.cc > b/libstdc++-v3/testsuite/std/time/year_month_day_last/io.cc > index 3241536a2e6..a57e56a1805 100644 > --- a/libstdc++-v3/testsuite/std/time/year_month_day_last/io.cc > +++ b/libstdc++-v3/testsuite/std/time/year_month_day_last/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/year_month_weekday/io.cc > b/libstdc++-v3/testsuite/std/time/year_month_weekday/io.cc > index 65baf1d37ae..e9a5bee7666 100644 > --- a/libstdc++-v3/testsuite/std/time/year_month_weekday/io.cc > +++ b/libstdc++-v3/testsuite/std/time/year_month_weekday/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/year_month_weekday_last/io.cc > b/libstdc++-v3/testsuite/std/time/year_month_weekday_last/io.cc > index 17f2244420d..fc2328ac001 100644 > --- a/libstdc++-v3/testsuite/std/time/year_month_weekday_last/io.cc > +++ b/libstdc++-v3/testsuite/std/time/year_month_weekday_last/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-namedlocale "fr_FR.ISO8859-15" } > // { dg-timeout-factor 2 } > diff --git a/libstdc++-v3/testsuite/std/time/zoned_time/1.cc > b/libstdc++-v3/testsuite/std/time/zoned_time/1.cc > index 1623aca1c7a..e773f50ae73 100644 > --- a/libstdc++-v3/testsuite/std/time/zoned_time/1.cc > +++ b/libstdc++-v3/testsuite/std/time/zoned_time/1.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-effective-target tzdb } > // { dg-require-effective-target cxx11_abi } > diff --git a/libstdc++-v3/testsuite/std/time/zoned_time/io.cc > b/libstdc++-v3/testsuite/std/time/zoned_time/io.cc > index 376b2734f19..c19e2a3c882 100644 > --- a/libstdc++-v3/testsuite/std/time/zoned_time/io.cc > +++ b/libstdc++-v3/testsuite/std/time/zoned_time/io.cc > @@ -1,3 +1,4 @@ > +// { dg-options "-lstdc++exp" { target c++23 } } > // { dg-do run { target c++20 } } > // { dg-require-effective-target cxx11_abi } > // { dg-timeout-factor 2 } -- Arsen Arsenović