https://gcc.gnu.org/g:473dde525248a694c0f4e62b31a7fc24b238c5b0
commit r15-9517-g473dde525248a694c0f4e62b31a7fc24b238c5b0 Author: Tomasz Kamiński <tkami...@redhat.com> Date: Mon Apr 14 16:00:57 2025 +0200 libstdc++: Implement formatters for pair and tuple [PR109162] This patch implements formatter specializations for pair and tuple form P2286R8. In addition using 'm` and range_format::map (from P2585R1) for ranges are now supported. The formatters for pairs and tuples whose corresponding elements are the same (after applying remove_cvref_t) derive from the same __tuple_formatter class. This reduce the code duplication, as most of the parsing and formatting is the same in such cases. We use a custom reduced implementation of the tuple (__formatters_storage) to store the elements formatters. Handling of the padding (width and fill) options, is extracted to __format::__format_padded function, that is used both by __tuple_formatter and range_formatter. To reduce number of instantations range_formatter::format triggers, we cast incoming range to __format::__maybe_const_range<_Rg, _CharT>&, before formatting it. As in the case of previous commits, the signatures of the user-facing parse and format methods of the provided formatters deviate from the standard by constraining types of parameters: * _CharT is constrained __formatter::__char * basic_format_parse_context<_CharT> for parse argument * basic_format_context<_Out, _CharT> for format second argument The standard specifies last three of above as unconstrained types. Finally, test for tuple-like std::array and std::ranges::subrange, that illustrate that they remain formatted as ranges. PR libstdc++/109162 libstdc++-v3/ChangeLog: * include/std/format (__formatter_int::_M_format_character_escaped) (__formatter_str::format): Use __sink.out() to produce _Sink_iter. (__format::__const_formattable_range): Moved closer to range_formatter. (__format::__maybe_const_range): Use `__conditional_t` and moved closer to range_formatter. (__format::__format_padded, __format::maybe_const) (__format::__indexed_formatter_storage, __format::__tuple_formatter) (std::formatter<pair<_Fp, _Sp>, _CharT>>) (std::formatter<tuple<_Tps...>, _CharT): Define. (std::formatter<_Rg, _CharT>::format): Cast incoming range to __format::__maybe_const_range<_Rg, _CharT>&. (std::formatter<_Rg, _CharT>::_M_format): Extracted from format, and use __format_padded. (std::formatter<_Rg, _CharT>::_M_format_no_padding): Rename... (std::formatter<_Rg, _CharT>::_M_format_elems): ...to this. (std::formatter<_Rg, _CharT>::_M_format_with_padding): Extracted as __format_padded. * testsuite/util/testsuite_iterators.h (test_input_range_nocopy): Define. * testsuite/std/format/ranges/formatter.cc: Tests for `m` specifier. * testsuite/std/format/ranges/sequence.cc: Tests for array and subrange. * testsuite/std/format/ranges/map.cc: New test. * testsuite/std/format/tuple.cc: New test. Reviewed-by: Jonathan Wakely <jwak...@redhat.com> Signed-off-by: Tomasz Kamiński <tkami...@redhat.com> Diff: --- libstdc++-v3/include/std/format | 377 +++++++++++++++++---- .../testsuite/std/format/ranges/formatter.cc | 6 +- libstdc++-v3/testsuite/std/format/ranges/map.cc | 209 ++++++++++++ .../testsuite/std/format/ranges/sequence.cc | 52 ++- libstdc++-v3/testsuite/std/format/tuple.cc | 259 ++++++++++++++ libstdc++-v3/testsuite/util/testsuite_iterators.h | 3 + 6 files changed, 813 insertions(+), 93 deletions(-) diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 096dda4f989c..b1455977c65f 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -1350,8 +1350,7 @@ namespace __format __fc, _M_spec); __format::_Str_sink<_CharT> __sink; - __format::_Sink_iter<_CharT> __out(__sink); - __format::__write_escaped(__out, __s, __term); + __format::__write_escaped(__sink.out(), __s, __term); basic_string_view<_CharT> __escaped(__sink.view().data(), __sink.view().size()); const size_t __escaped_width = _S_trunc(__escaped, __prec); @@ -1387,13 +1386,13 @@ namespace __format { ranges::iterator_t<_Rg> __first = ranges::begin(__rg); ranges::subrange __sub(__first, __first + __n); - return format(_String(from_range, __sub), __fc); + return format(_String(from_range, __sub), __fc); } else { // N.B. preserve the computed size ranges::subrange __sub(__rg, __n); - return format(_String(from_range, __sub), __fc); + return format(_String(from_range, __sub), __fc); } } else @@ -1698,7 +1697,7 @@ namespace __format template<typename _Out> typename basic_format_context<_Out, _CharT>::iterator _M_format_character_escaped(_CharT __c, - basic_format_context<_Out, _CharT>& __fc) const + basic_format_context<_Out, _CharT>& __fc) const { using _Esc = _Escapes<_CharT>; constexpr auto __term = __format::_Term_char::_Tc_apos; @@ -1708,8 +1707,7 @@ namespace __format _CharT __buf[12]; __format::_Fixedbuf_sink<_CharT> __sink(__buf); - __format::_Sink_iter<_CharT> __out(__sink); - __format::__write_escaped(__out, __in, __term); + __format::__write_escaped(__sink.out(), __in, __term); const basic_string_view<_CharT> __escaped = __sink.view(); size_t __estimated_width; @@ -3077,19 +3075,6 @@ namespace __format concept formattable = __format::__formattable_impl<remove_reference_t<_Tp>, _CharT>; - /// @cond undocumented -namespace __format -{ - template<typename _Rg, typename _CharT> - concept __const_formattable_range - = ranges::input_range<const _Rg> - && formattable<ranges::range_reference_t<const _Rg>, _CharT>; - - template<typename _Rg, typename _CharT> - using __maybe_const_range - = conditional_t<__const_formattable_range<_Rg, _CharT>, const _Rg, _Rg>; -} // namespace __format - /// @endcond #endif // format_ranges /// An iterator after the last character written, and the number of @@ -5209,6 +5194,246 @@ namespace __format /// @cond undocumented namespace __format { + template<typename _CharT, typename _Out, typename _Callback> + typename basic_format_context<_Out, _CharT>::iterator + __format_padded(basic_format_context<_Out, _CharT>& __fc, + const _Spec<_CharT>& __spec, + _Callback&& __call) + { + // This is required to implement formatting with padding, + // as we need to format to temporary buffer, using the same iterator. + static_assert(is_same_v<_Out, __format::_Sink_iter<_CharT>>); + + if (__spec._M_get_width(__fc) == 0) + return __call(__fc); + + struct _Restore_out + { + _Restore_out(basic_format_context<_Sink_iter<_CharT>, _CharT>& __fc) + : _M_ctx(std::addressof(__fc)), _M_out(__fc.out()) + { } + + void _M_trigger() + { + if (_M_ctx) + _M_ctx->advance_to(_M_out); + _M_ctx = nullptr; + } + + ~_Restore_out() + { _M_trigger(); } + + private: + basic_format_context<_Sink_iter<_CharT>, _CharT>* _M_ctx; + _Sink_iter<_CharT> _M_out; + }; + + _Restore_out __restore(__fc); + // TODO Consider double sinking, first buffer of width + // size and then original sink, if first buffer is overun + // we do not need to align + _Str_sink<_CharT> __buf; + __fc.advance_to(__buf.out()); + __call(__fc); + __restore._M_trigger(); + + basic_string_view<_CharT> __str(__buf.view()); + size_t __width; + if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>()) + __width = __unicode::__field_width(__str); + else + __width = __str.size(); + + return __format::__write_padded_as_spec(__str, __width, __fc, __spec); + } + + template<typename _Rg, typename _CharT> + concept __const_formattable_range + = ranges::input_range<const _Rg> + && formattable<ranges::range_reference_t<const _Rg>, _CharT>; + + template<typename _Rg, typename _CharT> + using __maybe_const_range + = __conditional_t<__const_formattable_range<_Rg, _CharT>, const _Rg, _Rg>; + + template<typename _Tp, typename _CharT> + using __maybe_const + = __conditional_t<formattable<const _Tp, _CharT>, const _Tp, _Tp>; + + template<size_t _Pos, typename _Tp, typename _CharT> + struct __indexed_formatter_storage + { + constexpr void + _M_parse() + { + basic_format_parse_context<_CharT> __pc({}); + if (_M_formatter.parse(__pc) != __pc.end()) + __format::__failed_to_parse_format_spec(); + } + + template<typename _Out> + void + _M_format(__maybe_const<_Tp, _CharT>& __elem, + basic_format_context<_Out, _CharT>& __fc, + basic_string_view<_CharT> __sep) const + { + if constexpr (_Pos != 0) + __fc.advance_to(__format::__write(__fc.out(), __sep)); + __fc.advance_to(_M_formatter.format(__elem, __fc)); + } + + [[__gnu__::__always_inline__]] + constexpr void + set_debug_format() + { + if constexpr (__has_debug_format<formatter<_Tp, _CharT>>) + _M_formatter.set_debug_format(); + } + + private: + formatter<_Tp, _CharT> _M_formatter; + }; + + template<typename _CharT, typename... _Tps> + class __tuple_formatter + { + using _String_view = basic_string_view<_CharT>; + using _Seps = __format::_Separators<_CharT>; + + public: + constexpr void + set_separator(basic_string_view<_CharT> __sep) noexcept + { _M_sep = __sep; } + + constexpr void + set_brackets(basic_string_view<_CharT> __open, + basic_string_view<_CharT> __close) noexcept + { + _M_open = __open; + _M_close = __close; + } + + // We deviate from standard, that declares this as template accepting + // unconstrained ParseContext type, which seems unimplementable. + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { + auto __first = __pc.begin(); + const auto __last = __pc.end(); + __format::_Spec<_CharT> __spec{}; + + auto __finished = [&] + { + if (__first != __last && *__first != '}') + return false; + + _M_spec = __spec; + _M_felems._M_parse(); + _M_felems.set_debug_format(); + return true; + }; + + if (__finished()) + return __first; + + __first = __spec._M_parse_fill_and_align(__first, __last, "{:"); + if (__finished()) + return __first; + + __first = __spec._M_parse_width(__first, __last, __pc); + if (__finished()) + return __first; + + if (*__first == 'n') + { + ++__first; + _M_open = _M_close = _String_view(); + } + else if (*__first == 'm') + { + ++__first; + if constexpr (sizeof...(_Tps) == 2) + { + _M_sep = _Seps::_S_colon(); + _M_open = _M_close = _String_view(); + } + else + __throw_format_error("format error: 'm' specifier requires range" + " of pair or tuple of two elements"); + } + + if (__finished()) + return __first; + + __format::__failed_to_parse_format_spec(); + } + + protected: + template<typename _Tuple, typename _Out, size_t... _Ids> + typename basic_format_context<_Out, _CharT>::iterator + _M_format(_Tuple& __tuple, index_sequence<_Ids...>, + basic_format_context<_Out, _CharT>& __fc) const + { return _M_format_elems(std::get<_Ids>(__tuple)..., __fc); } + + template<typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + _M_format_elems(__maybe_const<_Tps, _CharT>&... __elems, + basic_format_context<_Out, _CharT>& __fc) const + { + return __format::__format_padded( + __fc, _M_spec, + [this, &__elems...](basic_format_context<_Out, _CharT>& __nfc) + { + __nfc.advance_to(__format::__write(__nfc.out(), _M_open)); + _M_felems._M_format(__elems..., __nfc, _M_sep); + return __format::__write(__nfc.out(), _M_close); + }); + } + + private: + template<size_t... _Ids> + struct __formatters_storage + : __indexed_formatter_storage<_Ids, _Tps, _CharT>... + { + template<size_t _Id, typename _Up> + using _Base = __indexed_formatter_storage<_Id, _Up, _CharT>; + + constexpr void + _M_parse() + { + (_Base<_Ids, _Tps>::_M_parse(), ...); + } + + template<typename _Out> + void + _M_format(__maybe_const<_Tps, _CharT>&... __elems, + basic_format_context<_Out, _CharT>& __fc, + _String_view __sep) const + { + (_Base<_Ids, _Tps>::_M_format(__elems, __fc, __sep), ...); + } + + constexpr void + set_debug_format() + { + (_Base<_Ids, _Tps>::set_debug_format(), ...); + } + }; + + template<size_t... _Ids> + static auto + _S_create_storage(index_sequence<_Ids...>) + -> __formatters_storage<_Ids...>; + using _Formatters + = decltype(_S_create_storage(index_sequence_for<_Tps...>())); + + _Spec<_CharT> _M_spec{}; + _String_view _M_open = _Seps::_S_parens().substr(0, 1); + _String_view _M_close = _Seps::_S_parens().substr(1, 1); + _String_view _M_sep = _Seps::_S_comma(); + _Formatters _M_felems; + }; + template<typename _Tp> concept __is_map_formattable = __is_pair<_Tp> || (__is_tuple_v<_Tp> && tuple_size_v<_Tp> == 2); @@ -5216,6 +5441,46 @@ namespace __format } // namespace __format /// @endcond + // [format.tuple] Tuple formatter + template<__format::__char _CharT, formattable<_CharT> _Fp, + formattable<_CharT> _Sp> + struct formatter<pair<_Fp, _Sp>, _CharT> + : __format::__tuple_formatter<_CharT, remove_cvref_t<_Fp>, + remove_cvref_t<_Sp>> + { + private: + using __maybe_const_pair + = __conditional_t<formattable<const _Fp, _CharT> + && formattable<const _Sp, _CharT>, + const pair<_Fp, _Sp>, pair<_Fp, _Sp>>; + public: + // We deviate from standard, that declares this as template accepting + // unconstrained FormatContext type, which seems unimplementable. + template<typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + format(__maybe_const_pair& __p, + basic_format_context<_Out, _CharT>& __fc) const + { return this->_M_format_elems(__p.first, __p.second, __fc); } + }; + + template<__format::__char _CharT, formattable<_CharT>... _Tps> + struct formatter<tuple<_Tps...>, _CharT> + : __format::__tuple_formatter<_CharT, remove_cvref_t<_Tps>...> + { + private: + using __maybe_const_tuple + = __conditional_t<(formattable<const _Tps, _CharT> && ...), + const tuple<_Tps...>, tuple<_Tps...>>; + public: + // We deviate from standard, that declares this as template accepting + // unconstrained FormatContext type, which seems unimplementable. + template<typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + format(__maybe_const_tuple& __t, + basic_format_context<_Out, _CharT>& __fc) const + { return this->_M_format(__t, index_sequence_for<_Tps...>(), __fc); } + }; + // [format.range.formatter], class template range_formatter template<typename _Tp, __format::__char _CharT = char> requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT> @@ -5369,9 +5634,16 @@ namespace __format typename basic_format_context<_Out, _CharT>::iterator format(_Rg&& __rg, basic_format_context<_Out, _CharT>& __fc) const { - // This is required to implement formatting with padding, - // as we need to format to temporary buffer, using the same iterator. - static_assert(is_same_v<_Out, __format::_Sink_iter<_CharT>>); + using __maybe_const_range + = __format::__maybe_const_range<_Rg, _CharT>; + return _M_format<__maybe_const_range>(__rg, __fc); + } + + private: + template<ranges::input_range _Rg, typename _Out> + typename basic_format_context<_Out, _CharT>::iterator + _M_format(_Rg& __rg, basic_format_context<_Out, _CharT>& __fc) const + { if constexpr (same_as<_Tp, _CharT>) if (_M_spec._M_type == __format::_Pres_str || _M_spec._M_type == __format::_Pres_esc) @@ -5379,16 +5651,17 @@ namespace __format __format::__formatter_str __fstr(_M_spec); return __fstr._M_format_range(__rg, __fc); } - if (_M_spec._M_get_width(__fc) > 0) - return _M_format_with_padding(__rg, __fc); - return _M_format_no_padding(__rg, __fc); + return __format::__format_padded( + __fc, _M_spec, + [this, &__rg](basic_format_context<_Out, _CharT>& __nfc) + { return _M_format_elems(__rg, __nfc); }); } - private: + template<ranges::input_range _Rg, typename _Out> typename basic_format_context<_Out, _CharT>::iterator - _M_format_no_padding(_Rg& __rg, - basic_format_context<_Out, _CharT>& __fc) const + _M_format_elems(_Rg& __rg, + basic_format_context<_Out, _CharT>& __fc) const { auto __out = __format::__write(__fc.out(), _M_open); @@ -5409,50 +5682,6 @@ namespace __format return __format::__write(__out, _M_close); } - template<ranges::input_range _Rg, typename _Out> - typename basic_format_context<_Out, _CharT>::iterator - _M_format_with_padding(_Rg& __rg, - basic_format_context<_Out, _CharT>& __fc) const - { - struct _Restore_out - { - _Restore_out(basic_format_context<_Out, _CharT>& __fc) - : _M_ctx(addressof(__fc)), _M_out(__fc.out()) - { } - - void trigger() - { - if (_M_ctx) - _M_ctx->advance_to(_M_out); - _M_ctx = nullptr; - } - - ~_Restore_out() - { trigger(); } - - private: - basic_format_context<_Out, _CharT>* _M_ctx; - __format::_Sink_iter<_CharT> _M_out; - }; - - _Restore_out __restore{__fc}; - // TODO Consider double sinking, first buffer of width - // size and then original sink, if first buffer is overrun - // we do not need to align - __format::_Str_sink<_CharT> __buf; - __fc.advance_to(__format::_Sink_iter<_CharT>(__buf)); - _M_format_no_padding(__rg, __fc); - __restore.trigger(); - - _String_view __s(__buf.view()); - size_t __width; - if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>()) - __width = __unicode::__field_width(__s); - else - __width = __s.size(); - return __format::__write_padded_as_spec(__s, __width, __fc, _M_spec); - } - __format::_Spec<_CharT> _M_spec{}; _String_view _M_open = _Seps::_S_squares().substr(0, 1); _String_view _M_close = _Seps::_S_squares().substr(1, 1); @@ -5537,7 +5766,7 @@ namespace __format return _M_under._M_format_range(__rg, __fc); else return _M_under.format(__rg, __fc); - } + } private: using _Formatter_under diff --git a/libstdc++-v3/testsuite/std/format/ranges/formatter.cc b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc index 2045b51547ad..a4f5d9210ddf 100644 --- a/libstdc++-v3/testsuite/std/format/ranges/formatter.cc +++ b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc @@ -97,6 +97,7 @@ void test_override() { MyVector<_CharT, Formatter> vc{'a', 'b', 'c', 'd'}; + MyVector<std::pair<int, int>, Formatter> vp{{1, 11}, {2, 21}}; std::basic_string<_CharT> res; res = std::format(WIDEN("{:s}"), vc); @@ -106,7 +107,10 @@ test_override() res = std::format(WIDEN("{:+^6s}"), vc); VERIFY( res == WIDEN("+abcd+") ); - // TODO test map + res = std::format(WIDEN("{:m}"), vp); + VERIFY( res == WIDEN("{1: 11, 2: 21}") ); + res = std::format(WIDEN("{:=^20m}"), vp); + VERIFY( res == WIDEN("==={1: 11, 2: 21}===") ); } template<template<typename, typename> class Formatter> diff --git a/libstdc++-v3/testsuite/std/format/ranges/map.cc b/libstdc++-v3/testsuite/std/format/ranges/map.cc new file mode 100644 index 000000000000..34c5ed554b8b --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/ranges/map.cc @@ -0,0 +1,209 @@ +// { dg-do run { target c++23 } } + +#include <flat_map> +#include <format> +#include <list> +#include <map> +#include <span> +#include <testsuite_hooks.h> +#include <testsuite_iterators.h> +#include <vector> + +struct NotFormattable +{ + friend auto operator<=>(NotFormattable, NotFormattable) = default; +}; + +static_assert( !std::formattable<std::map<int, NotFormattable>, char> ); +static_assert( !std::formattable<std::map<NotFormattable, int>, wchar_t> ); + +template<typename... Args> +bool +is_format_string_for(const char* str, Args&&... args) +{ + try { + (void) std::vformat(str, std::make_format_args(args...)); + return true; + } catch (const std::format_error&) { + return false; + } +} + +template<typename... Args> +bool +is_format_string_for(const wchar_t* str, Args&&... args) +{ + try { + (void) std::vformat(str, std::make_wformat_args(args...)); + return true; + } catch (const std::format_error&) { + return false; + } +} + +template<typename Rg, typename CharT> +bool is_range_formatter_spec_for(CharT const* spec, Rg&& rg) +{ + using V = std::remove_cvref_t<std::ranges::range_reference_t<Rg>>; + std::range_formatter<V, CharT> fmt; + std::basic_format_parse_context<CharT> pc(spec); + try { + (void)fmt.parse(pc); + return true; + } catch (const std::format_error&) { + return false; + } +} + +#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S) +#define WIDEN(S) WIDEN_(_CharT, S) + +void +test_format_string() +{ + // only pair<T, U> amd tuple<T, U> value types are supported + VERIFY( !is_range_formatter_spec_for("m", std::vector<int>()) ); + VERIFY( !is_format_string_for("{:m}", std::vector<int>()) ); + VERIFY( !is_range_formatter_spec_for("m", std::vector<std::tuple<int, int, int>>()) ); + VERIFY( !is_format_string_for("{:m}", std::vector<std::tuple<int, int, int>>()) ); + + // invalid format stringss + VERIFY( !is_range_formatter_spec_for("?m", std::vector<std::pair<int, int>>()) ); + VERIFY( !is_format_string_for("{:?m}", std::vector<std::pair<int, int>>()) ); + VERIFY( !is_range_formatter_spec_for("m:", std::vector<std::pair<int, int>>()) ); + VERIFY( !is_format_string_for("{:m:}", std::vector<std::pair<int, int>>()) ); + + // precision is not supported + VERIFY( !is_range_formatter_spec_for(".10m", std::vector<std::pair<int, int>>()) ); + VERIFY( !is_format_string_for("{:.10m}", std::vector<std::pair<int, int>>()) ); + VERIFY( !is_format_string_for("{:.{}m}", std::vector<std::pair<int, int>>(), 10) ); + + // width needs to be integer type + VERIFY( !is_format_string_for("{:{}m}", std::vector<std::pair<int, int>>(), 1.0f) ); +} + +template<typename _CharT, typename Range> +void test_output(bool mapIsDefault) +{ + using Sv = std::basic_string_view<_CharT>; + using Pt = std::ranges::range_value_t<Range>; + using Ft = std::remove_cvref_t<std::tuple_element_t<0, Pt>>; + using St = std::remove_cvref_t<std::tuple_element_t<1, Pt>>; + auto makeRange = [](std::span<Pt> s) { + return Range(s.data(), s.data() + s.size()); + }; + + std::basic_string<_CharT> res; + size_t size = 0; + + Ft f1[]{1, 2, 3}; + St s1[]{11, 22, 33}; + Pt v1[]{{f1[0], s1[0]}, {f1[1], s1[1]}, {f1[2], s1[2]}}; + + res = std::format(WIDEN("{}"), makeRange(v1)); + if (mapIsDefault) + VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}") ); + else + VERIFY( res == WIDEN("[(1, 11), (2, 22), (3, 33)]") ); + + res = std::format(WIDEN("{:m}"), makeRange(v1)); + VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}") ); + res = std::format(WIDEN("{:nm}"), makeRange(v1)); + VERIFY( res == WIDEN("1: 11, 2: 22, 3: 33") ); + + res = std::format(WIDEN("{:3m}"), makeRange(v1)); + VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}") ); + + res = std::format(WIDEN("{:25m}"), makeRange(v1)); + VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33} ") ); + + res = std::format(WIDEN("{:{}m}"), makeRange(v1), 25); + VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33} ") ); + + res = std::format(WIDEN("{1:{0}m}"), 25, makeRange(v1)); + VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33} ") ); + + res = std::format(WIDEN("{:25nm}"), makeRange(v1)); + VERIFY( res == WIDEN("1: 11, 2: 22, 3: 33 ") ); + + res = std::format(WIDEN("{:*<23m}"), makeRange(v1)); + VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}**") ); + + res = std::format(WIDEN("{:->24m}"), makeRange(v1)); + VERIFY( res == WIDEN("---{1: 11, 2: 22, 3: 33}") ); + + res = std::format(WIDEN("{:=^25m}"), makeRange(v1)); + VERIFY( res == WIDEN("=={1: 11, 2: 22, 3: 33}==") ); + + res = std::format(WIDEN("{:=^25nm}"), makeRange(v1)); + VERIFY( res == WIDEN("===1: 11, 2: 22, 3: 33===") ); + + size = std::formatted_size(WIDEN("{:m}"), makeRange(v1)); + VERIFY( size == Sv(WIDEN("{1: 11, 2: 22, 3: 33}")).size() ); + + size = std::formatted_size(WIDEN("{:3m}"), makeRange(v1)); + VERIFY( size == Sv(WIDEN("{1: 11, 2: 22, 3: 33}")).size() ); + + size = std::formatted_size(WIDEN("{:25m}"), makeRange(v1)); + VERIFY( size == 25 ); +} + +template<class Range> +void test_output_c(bool mapIsDefault = false) +{ + test_output<char, Range>(mapIsDefault); + test_output<wchar_t, Range>(mapIsDefault); +} + +template<template<typename> class RangeT> +void test_output_pc() +{ + test_output_c<RangeT<std::pair<int, int>>>(); + test_output_c<RangeT<std::pair<const int, int>>>(); + test_output_c<RangeT<std::tuple<const int&, int&>>>(); +} + +void +test_outputs() +{ + using namespace __gnu_test; + test_output_c<std::map<int, int>>(true); + test_output_c<std::flat_map<int, int>>(true); + + test_output_pc<std::vector>(); + test_output_pc<std::list>(); + test_output_pc<std::span>(); + + test_output_pc<test_forward_range>(); + test_output_pc<test_input_range>(); + test_output_pc<test_input_range_nocopy>(); +} + +void +test_nested() +{ + std::vector<std::map<int, std::string>> vm{ + {{1, "one"}, {2, "two"}}, + {{1, "jeden"}, {2, "dwa"}}, + }; + std::string res; + + res = std::format("{}", vm); + VERIFY( res == R"([{1: "one", 2: "two"}, {1: "jeden", 2: "dwa"}])" ); + res = std::format("{:n:n}", vm); + VERIFY( res == R"(1: "one", 2: "two", 1: "jeden", 2: "dwa")" ); + + std::map<std::string, std::vector<std::string>> mv{ + {"english", {"zero", "one", "two"}}, + {"polish", {"zero", "jeden", "dwa"}}, + }; + res = std::format("{}", mv); + VERIFY( res == R"({"english": ["zero", "one", "two"], "polish": ["zero", "jeden", "dwa"]})" ); +} + +int main() +{ + test_format_string(); + test_outputs(); + test_nested(); +} diff --git a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc index 06574379ed50..61fc68ea252d 100644 --- a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc +++ b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc @@ -1,7 +1,9 @@ // { dg-do run { target c++23 } } +#include <array> #include <format> #include <list> +#include <ranges> #include <span> #include <testsuite_hooks.h> #include <testsuite_iterators.h> @@ -73,19 +75,23 @@ test_format_string() #define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S) #define WIDEN(S) WIDEN_(_CharT, S) -template<typename _CharT, typename Range> +template<typename _CharT, typename Range, typename Storage> void test_output() { using Sv = std::basic_string_view<_CharT>; using T = std::ranges::range_value_t<Range>; - auto makeRange = [](std::span<T> s) { - return Range(s.data(), s.data() + s.size()); + auto makeRange = [](Storage& s) -> Range { + if constexpr (std::is_same_v<std::remove_cvref_t<Range>, Storage>) + return s; + else + return Range(std::ranges::data(s), + std::ranges::data(s) + std::ranges::size(s)); }; std::basic_string<_CharT> res; size_t size = 0; - T v1[]{1, 2, 3}; + Storage v1{1, 2, 3}; res = std::format(WIDEN("{}"), makeRange(v1)); VERIFY( res == WIDEN("[1, 2, 3]") ); res = std::format(WIDEN("{:}"), makeRange(v1)); @@ -143,27 +149,37 @@ void test_output() VERIFY( size == 25 ); } -template<typename Range> -void test_output_c() +template<typename Cont> +void test_output_cont() { - test_output<char, Range>(); - test_output<wchar_t, Range>(); + test_output<char, Cont&, Cont>(); + test_output<wchar_t, Cont const&, Cont>(); +} + +template<typename View> +void test_output_view() +{ + test_output<char, View, int[3]>(); + test_output<wchar_t, View, int[3]>(); } void test_outputs() { using namespace __gnu_test; - test_output_c<std::vector<int>>(); - test_output_c<std::list<int>>(); - test_output_c<std::span<int>>(); - - test_output_c<test_forward_range<int>>(); - test_output_c<test_input_range<int>>(); - test_output_c<test_range_nocopy<int, input_iterator_wrapper_nocopy>>(); - - test_output_c<std::span<const int>>(); - test_output_c<test_forward_range<const int>>(); + test_output_cont<std::vector<int>>(); + test_output_cont<std::list<int>>(); + test_output_cont<std::array<int, 3>>(); + + test_output_view<std::span<int>>(); + test_output_view<std::ranges::subrange<int*>>(); + test_output_view<test_forward_range<int>>(); + test_output_view<test_input_range<int>>(); + test_output_view<test_input_range_nocopy<int>>(); + + test_output_view<std::span<const int>>(); + test_output_view<std::ranges::subrange<const int*>>(); + test_output_view<test_forward_range<const int>>(); } void diff --git a/libstdc++-v3/testsuite/std/format/tuple.cc b/libstdc++-v3/testsuite/std/format/tuple.cc new file mode 100644 index 000000000000..62f9d293aab5 --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/tuple.cc @@ -0,0 +1,259 @@ +// { dg-do run { target c++23 } } + +#include <format> +#include <string> +#include <testsuite_hooks.h> +#include <tuple> +#include <utility> + +struct NotFormattable +{}; + +static_assert( !std::formattable<std::pair<int, NotFormattable>, char> ); +static_assert( !std::formattable<std::tuple<int, NotFormattable, int>, wchar_t> ); + +template<typename... Args> +bool +is_format_string_for(const char* str, Args&&... args) +{ + try { + (void) std::vformat(str, std::make_format_args(args...)); + return true; + } catch (const std::format_error&) { + return false; + } +} + +template<typename... Args> +bool +is_format_string_for(const wchar_t* str, Args&&... args) +{ + try { + (void) std::vformat(str, std::make_wformat_args(args...)); + return true; + } catch (const std::format_error&) { + return false; + } +} + +#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S) +#define WIDEN(S) WIDEN_(_CharT, S) + +void +test_format_string() +{ + // invalid format stringss + VERIFY( !is_format_string_for("{:p}", std::tuple<>()) ); + VERIFY( !is_format_string_for("{:nm}", std::tuple<>()) ); + + // 'm' is only valid for 2 elemenst + VERIFY( !is_format_string_for("{:m}", std::tuple<>()) ); + VERIFY( !is_format_string_for("{:m}", std::tuple<int, int, int>()) ); + + // element specifier is not supported + VERIFY( !is_format_string_for("{::}", std::tuple<>()) ); + + // precision is not supported + VERIFY( !is_format_string_for("{:.10}", std::tuple<>()) ); + + // width needs to be integer type + VERIFY( !is_format_string_for("{:{}}", std::tuple<>(), 1.0f) ); +} + +template<typename _CharT> +void test_multi() +{ + using Sv = std::basic_string_view<_CharT>; + using Str = std::basic_string<_CharT>; + + std::basic_string<_CharT> res; + std::size_t size = 0; + std::tuple<int, Str, float> t1(1, WIDEN("test"), 2.1); + + res = std::format(WIDEN("{}"), t1); + VERIFY( res == WIDEN(R"((1, "test", 2.1))") ); + res = std::format(WIDEN("{:}"), t1); + VERIFY( res == WIDEN(R"((1, "test", 2.1))") ); + res = std::format(WIDEN("{:n}"), t1); + VERIFY( res == WIDEN(R"(1, "test", 2.1)") ); + + res = std::format(WIDEN("{:3}"), t1); + VERIFY( res == WIDEN(R"((1, "test", 2.1))") ); + + res = std::format(WIDEN("{:20}"), t1); + VERIFY( res == WIDEN(R"((1, "test", 2.1) )") ); + + res = std::format(WIDEN("{:{}}"), t1, 20); + VERIFY( res == WIDEN(R"((1, "test", 2.1) )") ); + + res = std::format(WIDEN("{1:{0}}"), 20, t1); + VERIFY( res == WIDEN(R"((1, "test", 2.1) )") ); + + res = std::format(WIDEN("{:^>17}"), t1); + VERIFY( res == WIDEN(R"(^(1, "test", 2.1))") ); + + res = std::format(WIDEN("{:$<18}"), t1); + VERIFY( res == WIDEN(R"((1, "test", 2.1)$$)") ); + + res = std::format(WIDEN("{:+^19}"), t1); + VERIFY( res == WIDEN(R"(+(1, "test", 2.1)++)") ); + + res = std::format(WIDEN("{:|^19n}"), t1); + VERIFY( res == WIDEN(R"(||1, "test", 2.1|||)") ); + + size = std::formatted_size(WIDEN("{}"), t1); + VERIFY( size == Sv(WIDEN(R"((1, "test", 2.1))")).size() ); + + size = std::formatted_size(WIDEN("{:3}"), t1); + VERIFY( size == Sv(WIDEN(R"((1, "test", 2.1))")).size() ); + + size = std::formatted_size(WIDEN("{:20}"), t1); + VERIFY( size == 20 ); + + std::tuple<int&, Str&, float&> t2 = t1; + res = std::format(WIDEN("{}"), t2); + VERIFY( res == WIDEN(R"((1, "test", 2.1))") ); + + std::tuple<int, int, int, int> t3(1, 2, 3, 4); + res = std::format(WIDEN("{}"), t3); + VERIFY( res == WIDEN(R"((1, 2, 3, 4))") ); + +} + +template<typename _CharT, typename Tuple> +void test_empty() +{ + std::basic_string<_CharT> res; + + Tuple e1; + res = std::format(WIDEN("{}"), e1); + VERIFY( res == WIDEN(R"(())") ); + + res = std::format(WIDEN("{:}"), e1); + VERIFY( res == WIDEN(R"(())") ); + + res = std::format(WIDEN("{:n}"), e1); + VERIFY( res == WIDEN(R"()") ); + + res = std::format(WIDEN("{:^>6}"), e1); + VERIFY( res == WIDEN(R"(^^^^())") ); +} + +template<typename _CharT, typename Pair> +void test_pair() +{ + using Ft = std::remove_cvref_t<std::tuple_element_t<0, Pair>>; + using St = std::remove_cvref_t<std::tuple_element_t<1, Pair>>; + + std::basic_string<_CharT> res; + + Ft f1 = 1; + St s1 = WIDEN("abc"); + Pair p1(f1, s1); + + res = std::format(WIDEN("{}"), p1); + VERIFY( res == WIDEN(R"((1, "abc"))") ); + + res = std::format(WIDEN("{:}"), p1); + VERIFY( res == WIDEN(R"((1, "abc"))") ); + + res = std::format(WIDEN("{:m}"), p1); + VERIFY( res == WIDEN(R"(1: "abc")") ); + + res = std::format(WIDEN("{:|^12m}"), p1); + VERIFY( res == WIDEN(R"(||1: "abc"||)") ); +} + +template<typename CharT, template<typename, typename> class PairT> +void test_pair_e() +{ + test_pair<CharT, PairT<int, std::basic_string<CharT>>>(); + test_pair<CharT, PairT<int, const CharT*>>(); + test_pair<CharT, PairT<const int, std::basic_string<CharT>>>(); + test_pair<CharT, PairT<int&, std::basic_string<CharT>&>>(); + test_pair<CharT, PairT<const int&, const std::basic_string<CharT>&>>(); +} + +template<typename Pair> +struct MyPair : Pair +{ + using Pair::Pair; +}; + +template<typename Pair, typename CharT> +struct std::formatter<MyPair<Pair>, CharT> +{ + constexpr formatter() noexcept + { + using _CharT = CharT; + _formatter.set_brackets(WIDEN("<"), WIDEN(">")); + _formatter.set_separator(WIDEN("; ")); + } + + constexpr std::basic_format_parse_context<CharT>::iterator + parse(std::basic_format_parse_context<CharT>& pc) + { return _formatter.parse(pc); } + + template<typename Out> + typename std::basic_format_context<Out, CharT>::iterator + format(const MyPair<Pair>& mp, + std::basic_format_context<Out, CharT>& fc) const + { return _formatter.format(mp, fc); } + +private: + std::formatter<Pair, CharT> _formatter; +}; + +template<typename _CharT, template<typename, typename> class PairT> +void test_custom() +{ + std::basic_string<_CharT> res; + MyPair<PairT<int, const _CharT*>> c1(1, WIDEN("abc")); + + res = std::format(WIDEN("{}"), c1); + VERIFY( res == WIDEN(R"(<1; "abc">)") ); + + res = std::format(WIDEN("{:}"), c1); + VERIFY( res == WIDEN(R"(<1; "abc">)") ); + + res = std::format(WIDEN("{:n}"), c1); + VERIFY( res == WIDEN(R"(1; "abc")") ); + + res = std::format(WIDEN("{:m}"), c1); + VERIFY( res == WIDEN(R"(1: "abc")") ); + + res = std::format(WIDEN("{:|^14}"), c1); + VERIFY( res == WIDEN(R"(||<1; "abc">||)") ); +} + +template<typename CharT> +void test_outputs() +{ + test_multi<CharT>(); + test_empty<CharT, std::tuple<>>(); + test_pair_e<CharT, std::pair>(); + test_pair_e<CharT, std::tuple>(); + test_custom<CharT, std::pair>(); + test_custom<CharT, std::tuple>(); +} + +void test_nested() +{ + std::string res; + std::tuple<std::tuple<>, std::pair<int, std::string>> tt{{}, {1, "abc"}}; + + res = std::format("{}", tt); + VERIFY( res == R"(((), (1, "abc")))" ); + res = std::format("{:n}", tt); + VERIFY( res == R"((), (1, "abc"))" ); + res = std::format("{:m}", tt); + VERIFY( res == R"((): (1, "abc"))" ); +} + +int main() +{ + test_format_string(); + test_outputs<char>(); + test_outputs<wchar_t>(); + test_nested(); +} diff --git a/libstdc++-v3/testsuite/util/testsuite_iterators.h b/libstdc++-v3/testsuite/util/testsuite_iterators.h index 20539ecaca61..74a87395cd7d 100644 --- a/libstdc++-v3/testsuite/util/testsuite_iterators.h +++ b/libstdc++-v3/testsuite/util/testsuite_iterators.h @@ -891,6 +891,9 @@ namespace __gnu_test template<typename T> using test_input_range = test_range<T, input_iterator_wrapper>; + template<typename T> + using test_input_range_nocopy + = test_range_nocopy<T, input_iterator_wrapper_nocopy>; template<typename T> using test_output_range = test_range<T, output_iterator_wrapper>;