From: Tomasz Kamiński Date: Fri, 18 Apr 2025 12:56:39 +0000 (+0200) Subject: libstdc++: Implement formatters for queue, priority_queue and stack [PR109162] X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bacf741a92a9a84becd23542b73186da4e4acbf6;p=thirdparty%2Fgcc.git libstdc++: Implement formatters for queue, priority_queue and stack [PR109162] This patch implements formatter specializations for standard container adaptors (queue, priority_queue and stack) from P2286R8. To be able to access the protected `c` member, the adaptors befriend corresponding formatter specializations. Note that such specialization may be disable if the container is formattable, in such case specializations are unharmful. 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 all above as unconstrained types. In particular _CharT constrain, allow us to befriend all allowed specializations. Furthermore the standard specifies these formatters as delegating to formatter, charT>, which in turn delegates to range_formatter. This patch avoids one level of indirection, and dependency of ranges::ref_view. This is technically observable if user specializes formatter> where PD is program defined container, but I do not think this is the case worth extra indirection. This patch also moves the formattable and it's dependencies to the formatfwd.h, so it can be used in adapters formatters, without including format header. The definition of _Iter_for is changed from alias to denoting back_insert_iterator>, to struct with type nested typedef that points to same type, that is forward declared. PR libstdc++/109162 libstdc++-v3/ChangeLog: * include/bits/formatfwd.h (__format::__parsable_with) (__format::__formattable_with, __format::__formattable_impl) (__format::__has_debug_format, __format::__const_formattable_range) (__format::__maybe_const_range, __format::__maybe_const) (std::formattable): Moved from std/format. (__format::Iter_for, std::range_formatter): Forward declare. * include/bits/stl_queue.h (std::formatter): Forward declare. (std::queue, std::priority_queue): Befriend formatter specializations. * include/bits/stl_stack.h (std::formatter): Forward declare. (std::stack): Befriend formatter specializations. * include/std/format (__format::_Iter_for): Define as struct with (__format::__parsable_with, __format::__formattable_with) (__format::__formattable_impl, __format::__has_debug_format) (_format::__const_formattable_range, __format::__maybe_const_range) (__format::__maybe_const, std::formattable): Moved to bits/formatfwd.h. (std::range_formatter): Remove default argument specified in declaration in bits/formatfwd.h. * include/std/queue: Include bits/version.h before bits/stl_queue.h. (formatter, _CharT>) (formatter, _CharT>): Define. * include/std/stack: Include bits/version.h before bits/stl_stack.h (formatter, _CharT>): Define. * testsuite/std/format/ranges/adaptors.cc: New test. Reviewed-by: Jonathan Wakely Signed-off-by: Tomasz Kamiński --- diff --git a/libstdc++-v3/include/bits/formatfwd.h b/libstdc++-v3/include/bits/formatfwd.h index a6b5ac8c8ce..9ba658b078a 100644 --- a/libstdc++-v3/include/bits/formatfwd.h +++ b/libstdc++-v3/include/bits/formatfwd.h @@ -37,6 +37,12 @@ // must have been included before this header: #ifdef __glibcxx_format // C++ >= 20 && HOSTED +#include +#include +#if __glibcxx_format_ranges // C++ >= 23 && HOSTED +# include // input_range, range_reference_t +#endif + namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION @@ -50,6 +56,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // [format.formatter], formatter template struct formatter; +/// @cond undocumented namespace __format { #ifdef _GLIBCXX_USE_WCHAR_T @@ -60,9 +67,80 @@ namespace __format concept __char = same_as<_CharT, char>; #endif + template>, + typename _ParseContext + = basic_format_parse_context> + concept __parsable_with + = semiregular<_Formatter> + && requires (_Formatter __f, _ParseContext __pc) + { + { __f.parse(__pc) } -> same_as; + }; + + template>, + typename _ParseContext + = basic_format_parse_context> + concept __formattable_with + = semiregular<_Formatter> + && requires (const _Formatter __cf, _Tp&& __t, _Context __fc) + { + { __cf.format(__t, __fc) } -> same_as; + }; + + // An unspecified output iterator type used in the `formattable` concept. + template + struct _Iter_for; + template + using _Iter_for_t = typename _Iter_for<_CharT>::type; + + template, _CharT>> + concept __formattable_impl + = __parsable_with<_Tp, _Context> && __formattable_with<_Tp, _Context>; + + template + concept __has_debug_format = requires(_Formatter __f) + { + __f.set_debug_format(); + }; + template<__char _CharT> struct __formatter_int; +} // namespace __format +/// @endcond + +#if __glibcxx_format_ranges // C++ >= 23 && HOSTED + // [format.formattable], concept formattable + template + concept formattable + = __format::__formattable_impl, _CharT>; + + template + requires same_as, _Tp> && formattable<_Tp, _CharT> + class range_formatter; + +/// @cond undocumented +namespace __format +{ + template + concept __const_formattable_range + = ranges::input_range + && formattable, _CharT>; + + template + using __maybe_const_range + = __conditional_t<__const_formattable_range<_Rg, _CharT>, const _Rg, _Rg>; + + template + using __maybe_const + = __conditional_t, const _Tp, _Tp>; } +#endif // format_ranges + _GLIBCXX_END_NAMESPACE_VERSION } // namespace std diff --git a/libstdc++-v3/include/bits/stl_queue.h b/libstdc++-v3/include/bits/stl_queue.h index 554e076aae9..a3a8bc1f0ad 100644 --- a/libstdc++-v3/include/bits/stl_queue.h +++ b/libstdc++-v3/include/bits/stl_queue.h @@ -70,6 +70,10 @@ namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION +#if __glibcxx_format_ranges + template class formatter; +#endif + /** * @brief A standard container giving FIFO behavior. * @@ -369,6 +373,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION swap(c, __q.c); } #endif // __cplusplus >= 201103L + +#if __glibcxx_format_ranges + friend class formatter, char>; + friend class formatter, wchar_t>; +#endif }; #if __cpp_deduction_guides >= 201606 @@ -898,6 +907,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION swap(comp, __pq.comp); } #endif // __cplusplus >= 201103L + +#if __glibcxx_format_ranges + friend class formatter, char>; + friend class formatter, wchar_t>; +#endif }; #if __cpp_deduction_guides >= 201606 diff --git a/libstdc++-v3/include/bits/stl_stack.h b/libstdc++-v3/include/bits/stl_stack.h index 7b324642b32..27c79d6ce58 100644 --- a/libstdc++-v3/include/bits/stl_stack.h +++ b/libstdc++-v3/include/bits/stl_stack.h @@ -70,6 +70,10 @@ namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION +#if __glibcxx_format_ranges + template class formatter; +#endif + /** * @brief A standard container giving FILO behavior. * @@ -343,6 +347,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION swap(c, __s.c); } #endif // __cplusplus >= 201103L + +#if __glibcxx_format_ranges + friend class formatter, char>; + friend class formatter, wchar_t>; +#endif }; #if __cpp_deduction_guides >= 201606 diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index e557e104d74..7d3067098be 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -117,6 +117,11 @@ namespace __format template class _Sink_iter; + // An unspecified output iterator type used in the `formattable` concept. + template + struct _Iter_for + { using type = back_insert_iterator>; }; + template using __format_context = basic_format_context<_Sink_iter<_CharT>, _CharT>; @@ -135,6 +140,7 @@ namespace __format template friend struct std::basic_format_string; }; + } // namespace __format /// @endcond @@ -3024,59 +3030,6 @@ namespace __format : private formatter<__format::__disabled, wchar_t> { }; #endif -/// @cond undocumented -namespace __format -{ - template>, - typename _ParseContext - = basic_format_parse_context> - concept __parsable_with - = semiregular<_Formatter> - && requires (_Formatter __f, _ParseContext __pc) - { - { __f.parse(__pc) } -> same_as; - }; - - template>, - typename _ParseContext - = basic_format_parse_context> - concept __formattable_with - = semiregular<_Formatter> - && requires (const _Formatter __cf, _Tp&& __t, _Context __fc) - { - { __cf.format(__t, __fc) } -> same_as; - }; - - // An unspecified output iterator type used in the `formattable` concept. - template - using _Iter_for = back_insert_iterator>; - - template, _CharT>> - concept __formattable_impl - = __parsable_with<_Tp, _Context> && __formattable_with<_Tp, _Context>; - - template - concept __has_debug_format = requires(_Formatter __f) - { - __f.set_debug_format(); - }; - -} // namespace __format -/// @endcond - -#if __glibcxx_format_ranges // C++ >= 23 && HOSTED - // [format.formattable], concept formattable - template - concept formattable - = __format::__formattable_impl, _CharT>; - -#endif // format_ranges - /// An iterator after the last character written, and the number of /// characters that would have been written. template @@ -5250,26 +5203,13 @@ namespace __format return __format::__write_padded_as_spec(__str, __width, __fc, __spec); } - template - concept __const_formattable_range - = ranges::input_range - && formattable, _CharT>; - // _Rg& and const _Rg& are both formattable and use same formatter // specialization for their references. template concept __simply_formattable_range = __const_formattable_range<_Rg, _CharT> && same_as>, - remove_cvref_t>>; - - template - using __maybe_const_range - = __conditional_t<__const_formattable_range<_Rg, _CharT>, const _Rg, _Rg>; - - template - using __maybe_const - = __conditional_t, const _Tp, _Tp>; + remove_cvref_t>>; template struct __indexed_formatter_storage @@ -5493,7 +5433,7 @@ namespace __format }; // [format.range.formatter], class template range_formatter - template + template requires same_as, _Tp> && formattable<_Tp, _CharT> class range_formatter { diff --git a/libstdc++-v3/include/std/queue b/libstdc++-v3/include/std/queue index 74b6c07b49f..90525897da7 100644 --- a/libstdc++-v3/include/std/queue +++ b/libstdc++-v3/include/std/queue @@ -61,14 +61,88 @@ #include // containers +#define __glibcxx_want_adaptor_iterator_pair_constructor +#define __glibcxx_want_containers_ranges +#include + #include #include #include #include #include -#define __glibcxx_want_adaptor_iterator_pair_constructor -#define __glibcxx_want_containers_ranges -#include +#ifdef __glibcxx_format_ranges // C++ >= 23 && HOSTED +#include + +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION + // Standard does not constrain accepted _CharT, we do so we can + // befriend specializations. + template<__format::__char _CharT, typename _Tp, + formattable<_CharT> _Container> + struct formatter, _CharT> + { + private: + using __maybe_const_adaptor + = __conditional_t< + __format::__const_formattable_range<_Container, _CharT>, + const queue<_Tp, _Container>, queue<_Tp, _Container>>; + + public: + // Standard declares this as template accepting unconstrained + // ParseContext type. + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { return _M_f.parse(__pc); } + + // Standard declares this as template accepting unconstrained + // FormatContext type. + template + typename basic_format_context<_Out, _CharT>::iterator + format(__maybe_const_adaptor& __a, + basic_format_context<_Out, _CharT>& __fc) const + { return _M_f.format(__a.c, __fc); } + + private: + // Standard uses formatter, _CharT>. + range_formatter<_Tp, _CharT> _M_f; + }; + + template<__format::__char _CharT, typename _Tp, + formattable<_CharT> _Container, typename _Compare> + struct formatter, _CharT> + { + private: + using __maybe_const_adaptor + = __conditional_t< + __format::__const_formattable_range<_Container, _CharT>, + const priority_queue<_Tp, _Container, _Compare>, + priority_queue<_Tp, _Container, _Compare>>; + + public: + // Standard declares this as template accepting unconstrained + // ParseContext type. + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { return _M_f.parse(__pc); } + + // Standard declares this as template accepting unconstrained + // FormatContext type. + template + typename basic_format_context<_Out, _CharT>::iterator + format(__maybe_const_adaptor& __a, + basic_format_context<_Out, _CharT>& __fc) const + { return _M_f.format(__a.c, __fc); } + + private: + // Standard uses formatter, _CharT>. + range_formatter<_Tp, _CharT> _M_f; + }; + +_GLIBCXX_END_NAMESPACE_VERSION +} // namespace std +#endif // __glibcxx_format_ranges + #endif /* _GLIBCXX_QUEUE */ diff --git a/libstdc++-v3/include/std/stack b/libstdc++-v3/include/std/stack index 5cea4762a19..a57a5a08bc3 100644 --- a/libstdc++-v3/include/std/stack +++ b/libstdc++-v3/include/std/stack @@ -61,11 +61,53 @@ #include // containers -#include -#include - #define __glibcxx_want_adaptor_iterator_pair_constructor #define __glibcxx_want_containers_ranges #include +#include +#include + +#ifdef __glibcxx_format_ranges // C++ >= 23 && HOSTED +#include + +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION + // Standard does not constrain accepted _CharT, we do so we can + // befriend specializations. + template<__format::__char _CharT, typename _Tp, + formattable<_CharT> _Container> + struct formatter, _CharT> + { + private: + using __maybe_const_adaptor + = __conditional_t< + __format::__const_formattable_range<_Container, _CharT>, + const stack<_Tp, _Container>, stack<_Tp, _Container>>; + + public: + // Standard declares this as template accepting unconstrained + // ParseContext type. + constexpr typename basic_format_parse_context<_CharT>::iterator + parse(basic_format_parse_context<_CharT>& __pc) + { return _M_f.parse(__pc); } + + // Standard declares this as template accepting unconstrained + // FormatContext type. + template + typename basic_format_context<_Out, _CharT>::iterator + format(__maybe_const_adaptor& __a, + basic_format_context<_Out, _CharT>& __fc) const + { return _M_f.format(__a.c, __fc); } + + private: + // Standard uses formatter, _CharT>. + range_formatter<_Tp, _CharT> _M_f; + }; +_GLIBCXX_END_NAMESPACE_VERSION +} // namespace std +#endif // __glibcxx_format_ranges + + #endif /* _GLIBCXX_STACK */ diff --git a/libstdc++-v3/testsuite/std/format/ranges/adaptors.cc b/libstdc++-v3/testsuite/std/format/ranges/adaptors.cc new file mode 100644 index 00000000000..854c7eef5bd --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/ranges/adaptors.cc @@ -0,0 +1,156 @@ +// { dg-do run { target c++23 } } +// { dg-timeout-factor 2 } + +#include +#include +#include +#include + +template +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; + } +} + +#define WIDEN_(C, S) ::std::__format::_Widen(S, L##S) +#define WIDEN(S) WIDEN_(_CharT, S) + +template class Adaptor> +void +test_format_string() +{ + Adaptor q; + VERIFY( !is_format_string_for("{:?}", q) ); + VERIFY( !is_format_string_for("{:P}", q) ); + + // width needs to be integer type + VERIFY( !is_format_string_for("{:{}}", q, 1.0f) ); +} + +struct NoFormat +{ + friend auto operator<=>(NoFormat, NoFormat) = default; +}; + +struct MutFormat +{ + MutFormat() = default; + MutFormat(int p) : x(p) {} + + int x; + friend auto operator<=>(MutFormat, MutFormat) = default; +}; + +template +struct std::formatter + : std::formatter +{ + template + Out format(MutFormat& mf, basic_format_context& ctx) const + { return std::formatter::format(mf.x, ctx); } +}; + +template +struct NotFormattableCont : std::vector +{ + using std::vector::vector; +}; + +template +constexpr auto std::format_kind> + = std::range_format::disabled; + +template> class Adaptor> +void +test_output() +{ + const std::vector v{3, 2, 1}; + std::basic_string<_CharT> res; + Adaptor> q(std::from_range, v); + + res = std::format(WIDEN("{}"), q); + VERIFY( res == WIDEN("[3, 2, 1]") ); + + res = std::format(WIDEN("{}"), std::as_const(q)); + VERIFY( res == WIDEN("[3, 2, 1]") ); + + res = std::format(WIDEN("{:n:#x}"), q); + VERIFY( res == WIDEN("0x3, 0x2, 0x1") ); + + res = std::format(WIDEN("{:=^23:#04x}"), q); + VERIFY( res == WIDEN("==[0x03, 0x02, 0x01]===") ); + + // Sequence output is always used + std::queue<_CharT, std::basic_string<_CharT>> qs( + std::from_range, + std::basic_string_view<_CharT>(WIDEN("321"))); + + res = std::format(WIDEN("{}"), qs); + VERIFY( res == WIDEN("['3', '2', '1']") ); + + res = std::format(WIDEN("{::}"), std::as_const(qs)); + VERIFY( res == WIDEN("[3, 2, 1]") ); + + res = std::format(WIDEN("{:?s}"), qs); + VERIFY( res == WIDEN(R"("321")") ); + + Adaptor> qd(std::from_range, v); + + res = std::format(WIDEN("{}"), qd); + VERIFY( res == WIDEN("[3, 2, 1]") ); + + res = std::format(WIDEN("{}"), std::as_const(qd)); + VERIFY( res == WIDEN("[3, 2, 1]") ); + + Adaptor mq(std::from_range, v); + + res = std::format(WIDEN("{}"), mq); + VERIFY( res == WIDEN("[3, 2, 1]") ); + + static_assert(!std::formattable, _CharT>); + + static_assert(!std::formattable, _CharT>); + static_assert(!std::formattable, _CharT>); + + // Formatter check if container is formattable, not container elements. + static_assert(!std::formattable>, _CharT>); +} + +template> class Adaptor> +void +test_adaptor() +{ + test_format_string(); + test_output(); + test_output(); + + static_assert(!std::formattable, int>); + static_assert(!std::formattable, char32_t>); +} + +template +void +test_compare() +{ + const std::vector v{3, 2, 1}; + std::basic_string<_CharT> res; + std::priority_queue, std::greater<>> q( + std::from_range, v); + + res = std::format(WIDEN("{}"), q); + VERIFY( res == WIDEN("[1, 2, 3]") ); +} + +int main() +{ + test_adaptor(); + test_adaptor(); + test_compare(); +}