This patch implements formatter specialization for input_ranges and range_formatter class form P2286R8, as adjusted by P2585R1. The formatter for pair/tuple is not yet provided, making maps not formattable.
To indicate partial support we define __glibcxx_format_ranges macro value 1, without defining __cpp_lib_format_ranges. This introduces an new _M_format_range member to internal __formatter_str, that formats range as _CharT as string, according the to the format spec. This function transform any contiguous range into basic_string_view direclty, by computing size if necessary. Otherwise, for ranges for which size can be computed (forward_range or sized_range) we use a stack buffer, if they are sufficiently small. Finally, we create a basic_string<_CharT> from the range, and format it content. In case when padding is specified, this is handled by firstly formatting the content of the range to the temporary string object. However, this can be only implemented if the iterator of the basic_format_context is internal type-erased iterator used by implementation. Otherwise a new basic_format_context would need to be created, which would require rebinding of handles stored in the arguments: note that format spec for element type could retrive any format argument from format context, visit and and user handle to format it. As basic_format_context provide no user-facing constructor, the user are not able to cosntructor object of that type with arbitrally iterators. The signatures of the user-facing parse and format method of the provided formatters deviate from the standard by constraining types of params: * _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. This types are later passed to possibly user-provided formatter specializations, that are required via formattable concept to only accept above types. Finally, the formatter<input_range, _CharT> specialization is implemented without using specialization of range-default-formatter exposition only template as base class, while providing same functionality. PR libstdc++/109162 libstdc++-v3/ChangeLog: * include/std/format (__format::__has_debug_format, _Pres_type::_Pres_seq) (_Pres_type::_Pres_str, __format::__Stackbuf_size): Define. (_Separators::_S_squares, _Separators::_S_parens, _Separators::_S_comma) (_Separators::_S_colon): Define additional constants. (_Spec::_M_parse_fill_and_align): Define overload accepting list of excluded characters for fill, and forward existing overload. (__formatter_str::_M_format_range): Define. (__format::_Buf_sink) Use __Stackbuf_size for size of array. (__format::__is_map_formattable, std::range_formatter) (std::formatter<_Rg, _CharT>): Define. * src/c++23/std.cc.in (std::format_kind, std::range_format) (std::range_formatter): Export. * testsuite/std/format/formatter/lwg3944.cc: Guarded tests with __glibcxx_format_ranges. * testsuite/std/format/formatter/requirements.cc: Adjusted for standard behavior. * testsuite/23_containers/vector/bool/format.cc: Test vector<bool> formatting. * testsuite/std/format/ranges/format_kind.cc: New test. * testsuite/std/format/ranges/formatter.cc: New test. * testsuite/std/format/ranges/sequence.cc: New test. * testsuite/std/format/ranges/string.cc: New test. --- Adjusted the commit message and added test for result of formattable check for ranges of types that are not formattable. libstdc++-v3/include/std/format | 511 ++++++++++++++++-- libstdc++-v3/src/c++23/std.cc.in | 6 + .../23_containers/vector/bool/format.cc | 6 + .../testsuite/std/format/formatter/lwg3944.cc | 4 +- .../std/format/formatter/requirements.cc | 14 +- .../std/format/ranges/format_kind.cc | 94 ++++ .../testsuite/std/format/ranges/formatter.cc | 145 +++++ .../testsuite/std/format/ranges/sequence.cc | 190 +++++++ .../testsuite/std/format/ranges/string.cc | 226 ++++++++ 9 files changed, 1131 insertions(+), 65 deletions(-) create mode 100644 libstdc++-v3/testsuite/std/format/ranges/format_kind.cc create mode 100644 libstdc++-v3/testsuite/std/format/ranges/formatter.cc create mode 100644 libstdc++-v3/testsuite/std/format/ranges/sequence.cc create mode 100644 libstdc++-v3/testsuite/std/format/ranges/string.cc diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 23f00970840..e2b02a804e1 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -97,6 +97,10 @@ namespace __format #define _GLIBCXX_WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S) #define _GLIBCXX_WIDEN(S) _GLIBCXX_WIDEN_(_CharT, S) + // Size for stack located buffer + template<typename _CharT> + constexpr size_t __Stackbuf_size = 32 * sizeof(void*) / sizeof(_CharT); + // Type-erased character sinks. template<typename _CharT> class _Sink; template<typename _CharT> class _Fixedbuf_sink; @@ -475,9 +479,10 @@ namespace __format _Pres_d = 1, _Pres_b, _Pres_B, _Pres_o, _Pres_x, _Pres_X, _Pres_c, // Presentation types for floating-point types. _Pres_a = 1, _Pres_A, _Pres_e, _Pres_E, _Pres_f, _Pres_F, _Pres_g, _Pres_G, - _Pres_p = 0, _Pres_P, // For pointers. - _Pres_s = 0, // For strings and bool. - _Pres_esc = 0xf, // For strings and charT. + _Pres_p = 0, _Pres_P, // For pointers. + _Pres_s = 0, // For strings, bool + _Pres_seq = 0, _Pres_str, // For ranges + _Pres_esc = 0xf, // For strings, charT and ranges }; enum _Align { @@ -544,42 +549,48 @@ namespace __format // pre: __first != __last constexpr iterator _M_parse_fill_and_align(iterator __first, iterator __last) noexcept + { return _M_parse_fill_and_align(__first, __last, "{"); } + + // pre: __first != __last + constexpr iterator + _M_parse_fill_and_align(iterator __first, iterator __last, string_view __not_fill) noexcept { - if (*__first != '{') + for (char c : __not_fill) + if (*__first == c) + return __first; + + using namespace __unicode; + if constexpr (__literal_encoding_is_unicode<_CharT>()) { - using namespace __unicode; - if constexpr (__literal_encoding_is_unicode<_CharT>()) - { - // Accept any UCS scalar value as fill character. - _Utf32_view<ranges::subrange<iterator>> __uv({__first, __last}); - if (!__uv.empty()) - { - auto __beg = __uv.begin(); - char32_t __c = *__beg++; - if (__is_scalar_value(__c)) - if (auto __next = __beg.base(); __next != __last) - if (_Align __align = _S_align(*__next)) - { - _M_fill = __c; - _M_align = __align; - return ++__next; - } - } - } - else if (__last - __first >= 2) - if (_Align __align = _S_align(__first[1])) - { - _M_fill = *__first; - _M_align = __align; - return __first + 2; - } + // Accept any UCS scalar value as fill character. + _Utf32_view<ranges::subrange<iterator>> __uv({__first, __last}); + if (!__uv.empty()) + { + auto __beg = __uv.begin(); + char32_t __c = *__beg++; + if (__is_scalar_value(__c)) + if (auto __next = __beg.base(); __next != __last) + if (_Align __align = _S_align(*__next)) + { + _M_fill = __c; + _M_align = __align; + return ++__next; + } + } + } + else if (__last - __first >= 2) + if (_Align __align = _S_align(__first[1])) + { + _M_fill = *__first; + _M_align = __align; + return __first + 2; + } - if (_Align __align = _S_align(__first[0])) - { - _M_fill = ' '; - _M_align = __align; - return __first + 1; - } + if (_Align __align = _S_align(__first[0])) + { + _M_fill = ' '; + _M_align = __align; + return __first + 1; } return __first; } @@ -934,11 +945,27 @@ namespace __format static consteval _Str_view _S_all() - { return _GLIBCXX_WIDEN("{}"); } + { return _GLIBCXX_WIDEN("[]{}(), : "); } static consteval - _Str_view _S_braces() + _Str_view _S_squares() { return _S_all().substr(0, 2); } + + static consteval + _Str_view _S_braces() + { return _S_all().substr(2, 2); } + + static consteval + _Str_view _S_parens() + { return _S_all().substr(4, 2); } + + static consteval + _Str_view _S_comma() + { return _S_all().substr(6, 2); } + + static consteval + _Str_view _S_colon() + { return _S_all().substr(8, 2); } }; template<typename _CharT> @@ -1231,6 +1258,13 @@ namespace __format template<__char _CharT> struct __formatter_str { + __formatter_str() = default; + + constexpr + __formatter_str(_Spec<_CharT> __spec) noexcept + : _M_spec(__spec) + { } + constexpr typename basic_format_parse_context<_CharT>::iterator parse(basic_format_parse_context<_CharT>& __pc) { @@ -1329,6 +1363,43 @@ namespace __format } #if __glibcxx_format_ranges // C++ >= 23 && HOSTED + template<ranges::input_range _Rg, class _Out> + requires same_as<remove_cvref_t<ranges::range_reference_t<_Rg>>, _CharT> + typename basic_format_context<_Out, _CharT>::iterator + _M_format_range(_Rg&& __rg, basic_format_context<_Out, _CharT>& __fc) const + { + using _String = basic_string<_CharT>; + using _String_view = basic_string_view<_CharT>; + if constexpr (ranges::forward_range<_Rg> || ranges::sized_range<_Rg>) + { + const size_t __n(ranges::distance(__rg)); + if constexpr (ranges::contiguous_range<_Rg>) + return format(_String_view(ranges::data(__rg), __n), __fc); + else if (__n <= __format::__Stackbuf_size<_CharT>) + { + _CharT __buf[__format::__Stackbuf_size<_CharT>]; + ranges::copy(__rg, __buf); + return format(_String_view(__buf, __n), __fc); + } + else if constexpr (ranges::sized_range<_Rg>) + return format(_String(from_range, __rg), __fc); + else if constexpr (ranges::random_access_range<_Rg>) + { + ranges::iterator_t<_Rg> __first = ranges::begin(__rg); + ranges::subrange __sub(__first, __first + __n); + 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); + } + } + else + return format(_String(from_range, __rg), __fc); + } + constexpr void set_debug_format() noexcept { _M_spec._M_type = _Pres_esc; } @@ -2931,7 +3002,7 @@ namespace __format }; /// @} -#if defined _GLIBCXX_USE_WCHAR_T && __cpp_lib_format_ranges +#if defined _GLIBCXX_USE_WCHAR_T && __glibcxx_format_ranges // _GLIBCXX_RESOLVE_LIB_DEFECTS // 3944. Formatters converting sequences of char to sequences of wchar_t @@ -2991,19 +3062,21 @@ namespace __format concept __formattable_impl = __parsable_with<_Tp, _Context> && __formattable_with<_Tp, _Context>; + template<typename _Formatter> + concept __has_debug_format = requires(_Formatter __f) + { + __f.set_debug_format(); + }; + } // namespace __format /// @endcond -// Concept std::formattable was introduced by P2286R8 "Formatting Ranges", -// but we can't guard it with __cpp_lib_format_ranges until we define that! -#if __cplusplus > 202002L +#if __glibcxx_format_ranges // C++ >= 23 && HOSTED // [format.formattable], concept formattable template<typename _Tp, typename _CharT> concept formattable = __format::__formattable_impl<remove_reference_t<_Tp>, _CharT>; -#endif -#if __cpp_lib_format_ranges /// @cond undocumented namespace __format { @@ -3246,7 +3319,7 @@ namespace __format class _Buf_sink : public _Sink<_CharT> { protected: - _CharT _M_buf[32 * sizeof(void*) / sizeof(_CharT)]; + _CharT _M_buf[__Stackbuf_size<_CharT>]; [[__gnu__::__always_inline__]] constexpr @@ -5088,7 +5161,7 @@ namespace __format } #endif -#if __cpp_lib_format_ranges +#if __glibcxx_format_ranges // C++ >= 23 && HOSTED // [format.range], formatting of ranges // [format.range.fmtkind], variable template format_kind enum class range_format { @@ -5133,28 +5206,352 @@ namespace __format template<ranges::input_range _Rg> requires same_as<_Rg, remove_cvref_t<_Rg>> constexpr range_format format_kind<_Rg> = __fmt_kind<_Rg>(); - // [format.range.formatter], class template range_formatter - template<typename _Tp, typename _CharT = char> - requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT> - class range_formatter; // TODO - /// @cond undocumented namespace __format { - // [format.range.fmtdef], class template range-default-formatter - template<range_format _Kind, ranges::input_range _Rg, typename _CharT> - struct __range_default_formatter; // TODO + template<typename _Tp> + concept __is_map_formattable + = __is_pair<_Tp> || (__is_tuple_v<_Tp> && tuple_size_v<_Tp> == 2); + } // namespace __format /// @endcond + // [format.range.formatter], class template range_formatter + template<class _Tp, __format::__char _CharT = char> + requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT> + class range_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; + } + + constexpr formatter<_Tp, _CharT>& + underlying() noexcept + { return _M_fval; } + + constexpr const formatter<_Tp, _CharT>& + underlying() const noexcept + { return _M_fval; } + + // 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{}; + bool __no_brace = false; + + auto __finished = [&] + { return __first == __last || *__first == '}'; }; + + auto __finalize = [&] + { + _M_spec = __spec; + return __first; + }; + + auto __parse_val = [&](_String_view __nfs = _String_view()) + { + basic_format_parse_context<_CharT> __npc(__nfs); + if (_M_fval.parse(__npc) != __npc.end()) + __format::__failed_to_parse_format_spec(); + if constexpr (__format::__has_debug_format<formatter<_Tp, _CharT>>) + _M_fval.set_debug_format(); + return __finalize(); + }; + + if (__finished()) + return __parse_val(); + + __first = __spec._M_parse_fill_and_align(__first, __last, "{:"); + if (__finished()) + return __parse_val(); + + __first = __spec._M_parse_width(__first, __last, __pc); + if (__finished()) + return __parse_val(); + + if (*__first == '?') + { + ++__first; + __spec._M_type = __format::_Pres_esc; + } + + if (__finished()) // only '?' + __throw_format_error("format error: '?' is allowed only in combination" + " with 's'"); + + if (*__first == 's') + { + ++__first; + if constexpr (same_as<_Tp, _CharT>) + { + if (__spec._M_type != __format::_Pres_esc) + __spec._M_type = __format::_Pres_str; + if (__finished()) + return __finalize(); + __throw_format_error("format error: element format specifier " + "cannot be provided when 's' specifier is used"); + } + else + __throw_format_error("format error: 's' specifier requires " + " range of character types"); + } + + if (__spec._M_type == __format::_Pres_esc) // only '?' + __throw_format_error("format error: '?' is allowed only in combination" + " with 's'"); + + if (__finished()) + return __parse_val(); + + if (*__first == 'n') + { + ++__first; + _M_open = _M_close = _String_view(); + __no_brace = true; + } + + if (__finished()) + return __parse_val(); + + if (*__first == 'm') + { + _String_view __m(__first, 1); + ++__first; + if constexpr (__format::__is_map_formattable<_Tp>) + { + _M_sep = _Seps::_S_comma(); + if (!__no_brace) + { + _M_open = _Seps::_S_braces().substr(0, 1); + _M_close = _Seps::_S_braces().substr(1, 1); + } + if (__finished()) + return __parse_val(__m); + __throw_format_error("format error: element format specifier " + "cannot be provided when 'm' specifier is used"); + + } + else + __throw_format_error("format error: 'm' specifier requires " + " range of pairs or tuples of two elements"); + } + + if (__finished()) + return __parse_val(); + + if (*__first == ':') + { + __pc.advance_to(++__first); + __first = _M_fval.parse(__pc); + } + + if (__finished()) + return __finalize(); + + __format::__failed_to_parse_format_spec(); + } + + // We deviate from standard, that declares this as template accepting + // unconstrained FormatContext type, which seems unimplementable. + template<ranges::input_range _Rg, class _Out> + requires formattable<ranges::range_reference_t<_Rg>, _CharT> && + same_as<remove_cvref_t<ranges::range_reference_t<_Rg>>, _Tp> + 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 itertor. + static_assert(is_same_v<_Out, __format::_Sink_iter<_CharT>>); + if constexpr (same_as<_Tp, _CharT>) + if (_M_spec._M_type == __format::_Pres_str + || _M_spec._M_type == __format::_Pres_esc) + { + __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); + } + + private: + template<ranges::input_range _Rg, class _Out> + typename basic_format_context<_Out, _CharT>::iterator + _M_format_no_padding(_Rg& __rg, + basic_format_context<_Out, _CharT>& __fc) const + { + auto __out = __format::__write(__fc.out(), _M_open); + + auto __first = ranges::begin(__rg); + auto const __last = ranges::end(__rg); + if (__first == __last) + return __format::__write(__out, _M_close); + + __fc.advance_to(__out); + __out = _M_fval.format(*__first, __fc); + for (++__first; __first != __last; ++__first) + { + __out = __format::__write(__out, _M_sep); + __fc.advance_to(__out); + __out = _M_fval.format(*__first, __fc); + } + + return __format::__write(__out, _M_close); + } + + template<ranges::input_range _Rg, class _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 overun + // 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); + _String_view _M_sep = _Seps::_S_comma(); + formatter<_Tp, _CharT> _M_fval; + }; + + // In standard this is shown as inherting from specialization of + // exposition only specialization for range-default-formatter for + // each range_format. We opt for simpler implementation. // [format.range.fmtmap], [format.range.fmtset], [format.range.fmtstr], // specializations for maps, sets, and strings - template<ranges::input_range _Rg, typename _CharT> + template<ranges::input_range _Rg, __format::__char _CharT> requires (format_kind<_Rg> != range_format::disabled) && formattable<ranges::range_reference_t<_Rg>, _CharT> struct formatter<_Rg, _CharT> - : __format::__range_default_formatter<format_kind<_Rg>, _Rg, _CharT> - { }; + { + private: + static const bool _S_range_format_is_string = + (format_kind<_Rg> == range_format::string) + || (format_kind<_Rg> == range_format::debug_string); + using _Vt = remove_cvref_t< + ranges::range_reference_t< + __format::__maybe_const_range<_Rg, _CharT>>>; + + static consteval bool _S_is_correct() + { + if constexpr (_S_range_format_is_string) + static_assert(same_as<_Vt, _CharT>); + return true; + } + + static_assert(_S_is_correct()); + + public: + constexpr formatter() noexcept + { + using _Seps = __format::_Separators<_CharT>; + if constexpr (format_kind<_Rg> == range_format::map) + { + static_assert(__format::__is_map_formattable<_Vt>); + _M_under.set_brackets(_Seps::_S_braces().substr(0, 1), + _Seps::_S_braces().substr(1, 1)); + _M_under.underlying().set_brackets({}, {}); + _M_under.underlying().set_separator(_Seps::_S_colon()); + } + else if constexpr (format_kind<_Rg> == range_format::set) + _M_under.set_brackets(_Seps::_S_braces().substr(0, 1), + _Seps::_S_braces().substr(1, 1)); + } + + constexpr void + set_separator(basic_string_view<_CharT> __sep) noexcept + requires (!_S_range_format_is_string) + { _M_under.set_separator(__sep); } + + constexpr void + set_brackets(basic_string_view<_CharT> __open, + basic_string_view<_CharT> __close) noexcept + requires (!_S_range_format_is_string) + { _M_under.set_brackets(__open, __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 __res = _M_under.parse(__pc); + if constexpr (format_kind<_Rg> == range_format::debug_string) + _M_under.set_debug_format(); + return __res; + } + + // 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(__format::__maybe_const_range<_Rg, _CharT>& __rg, + basic_format_context<_Out, _CharT>& __fc) const + { + if constexpr (_S_range_format_is_string) + return _M_under._M_format_range(__rg, __fc); + else + return _M_under.format(__rg, __fc); + } + + private: + using _Formatter_under + = __conditional_t<_S_range_format_is_string, + __format::__formatter_str<_CharT>, + range_formatter<_Vt, _CharT>>; + _Formatter_under _M_under; + }; #endif // C++23 formatting ranges #undef _GLIBCXX_WIDEN diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/std.cc.in index 12253b95c5a..5e18ad73908 100644 --- a/libstdc++-v3/src/c++23/std.cc.in +++ b/libstdc++-v3/src/c++23/std.cc.in @@ -1332,6 +1332,12 @@ export namespace std using std::wformat_context; using std::wformat_parse_context; using std::wformat_string; +// FIXME __cpp_lib_format_ranges +#ifdef __glibcxx_format_ranges + using std::format_kind; + using std::range_format; + using std::range_formatter; +#endif } // <forward_list> diff --git a/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc b/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc index 2586225dd05..eb24b66d82e 100644 --- a/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc +++ b/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc @@ -56,6 +56,12 @@ test_output() res = std::format(WIDEN("{:=^#7X}"), v[1]); VERIFY( res == WIDEN("==0X0==") ); + + res = std::format(WIDEN("{}"), v); + VERIFY( res == WIDEN("[true, false]") ); + + res = std::format(WIDEN("{::d}"), v); + VERIFY( res == WIDEN("[1, 0]") ); } int main() diff --git a/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc b/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc index ff5f075bcc8..1f3edc9cb03 100644 --- a/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc +++ b/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc @@ -4,6 +4,7 @@ // LWG 3944. Formatters converting sequences of char to sequences of wchar_t #include <format> +#include <vector> void test_lwg3944() { @@ -14,11 +15,10 @@ void test_lwg3944() std::format(L"{}",cstr); // { dg-error "here" } // Ill-formed in C++20 - // In C++23 they give L"['h', 'e', 'l', 'l', 'o']" std::format(L"{}", "hello"); // { dg-error "here" } std::format(L"{}", std::string_view("hello")); // { dg-error "here" } std::format(L"{}", std::string("hello")); // { dg-error "here" } -#ifdef __cpp_lib_format_ranges +#ifdef __glibcxx_format_ranges // LWG 3944 does not change this, it's still valid. std::format(L"{}", std::vector{'h', 'e', 'l', 'l', 'o'}); #endif diff --git a/libstdc++-v3/testsuite/std/format/formatter/requirements.cc b/libstdc++-v3/testsuite/std/format/formatter/requirements.cc index 416b9a8ede5..51f04f538ba 100644 --- a/libstdc++-v3/testsuite/std/format/formatter/requirements.cc +++ b/libstdc++-v3/testsuite/std/format/formatter/requirements.cc @@ -70,12 +70,14 @@ test_specializations() // [format.formatter.spec] // LWG 3833. Remove specialization // template<size_t N> struct formatter<const charT[N], charT> - using Farr = std::format_context::formatter_type<const char[1]>; - static_assert( ! std::is_default_constructible_v<Farr> ); - static_assert( ! std::is_copy_constructible_v<Farr> ); - static_assert( ! std::is_move_constructible_v<Farr> ); - static_assert( ! std::is_copy_assignable_v<Farr> ); - static_assert( ! std::is_move_assignable_v<Farr> ); + // Formatter is only expected to be instantiated with only cv-unqal types + // and attempting to instantiate this specialization is ill-formed + // using Farr = std::format_context::formatter_type<const char[1]>; + // static_assert( ! std::is_default_constructible_v<Farr> ); + // static_assert( ! std::is_copy_constructible_v<Farr> ); + // static_assert( ! std::is_move_constructible_v<Farr> ); + // static_assert( ! std::is_copy_assignable_v<Farr> ); + // static_assert( ! std::is_move_assignable_v<Farr> ); } int main() diff --git a/libstdc++-v3/testsuite/std/format/ranges/format_kind.cc b/libstdc++-v3/testsuite/std/format/ranges/format_kind.cc new file mode 100644 index 00000000000..14b9ff20c21 --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/ranges/format_kind.cc @@ -0,0 +1,94 @@ +// { dg-do run { target c++23 } } + +#include <deque> +#include <flat_map> +#include <flat_set> +#include <format> +#include <list> +#include <map> +#include <set> +#include <testsuite_hooks.h> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +static_assert( std::format_kind<std::vector<int>> == std::range_format::sequence ); +static_assert( std::format_kind<std::deque<int>> == std::range_format::sequence ); +static_assert( std::format_kind<std::list<int>> == std::range_format::sequence ); + +static_assert( std::format_kind<std::set<int>> == std::range_format::set ); +static_assert( std::format_kind<std::multiset<int>> == std::range_format::set ); +static_assert( std::format_kind<std::unordered_set<int>> == std::range_format::set ); +static_assert( std::format_kind<std::unordered_multiset<int>> == std::range_format::set ); +static_assert( std::format_kind<std::flat_set<int>> == std::range_format::set ); +static_assert( std::format_kind<std::flat_multiset<int>> == std::range_format::set ); + +static_assert( std::format_kind<std::map<int, int>> == std::range_format::map ); +static_assert( std::format_kind<std::multimap<int, int>> == std::range_format::map ); +static_assert( std::format_kind<std::unordered_map<int, int>> == std::range_format::map ); +static_assert( std::format_kind<std::unordered_multimap<int, int>> == std::range_format::map ); +static_assert( std::format_kind<std::flat_map<int, int>> == std::range_format::map ); +static_assert( std::format_kind<std::flat_multimap<int, int>> == std::range_format::map ); + +template<typename T> +struct MyVec : std::vector<T> +{}; + +static_assert( std::format_kind<MyVec<int>> == std::range_format::sequence ); + +template<typename T> +struct MySet : std::vector<T> +{ + using key_type = T; +}; + +static_assert( std::format_kind<MySet<int>> == std::range_format::set ); + +template<typename T> +struct MyMap : std::vector<T> +{ + using key_type = T; + using mapped_type = int; +}; + +static_assert( std::format_kind<MyMap<std::pair<int, int>>> == std::range_format::map ); +static_assert( std::format_kind<MyMap<std::tuple<int, int>>> == std::range_format::map ); +static_assert( std::format_kind<MyMap<int>> == std::range_format::set ); + +template<typename T, std::range_format rf> +struct CustFormat : std::vector<T> +{ + using std::vector<T>::vector; +}; + +template<typename T, std::range_format rf> +constexpr auto std::format_kind<CustFormat<T, rf>> = rf; + +void test_override() +{ + CustFormat<int, std::range_format::disabled> disabledf; + static_assert( !std::formattable<decltype(disabledf), char> ); + + CustFormat<int, std::range_format::sequence> seqf{1, 2, 3}; + VERIFY( std::format("{}", seqf) == "[1, 2, 3]" ); + + CustFormat<int, std::range_format::set> setf{1, 2, 3}; + VERIFY( std::format("{}", setf) == "{1, 2, 3}" ); + + // TODO test map once formatter for pair is implenented + + CustFormat<char, std::range_format::string> stringf{'a', 'b', 'c', 'd'}; + VERIFY( std::format("{}", stringf) == "abcd" ); + // Support precision as string do + VERIFY( std::format("{:.2}", stringf) == "ab" ); + + CustFormat<char, std::range_format::debug_string> debugf{'a', 'b', 'c', 'd'}; + VERIFY( std::format("{}", debugf) == R"("abcd")" ); + // Support precision as string do + VERIFY( std::format("{:.3}", debugf) == R"("ab)" ); +} + +int main() +{ + test_override(); +} diff --git a/libstdc++-v3/testsuite/std/format/ranges/formatter.cc b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc new file mode 100644 index 00000000000..2045b51547a --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc @@ -0,0 +1,145 @@ +// { dg-do run { target c++23 } } + +#include <format> +#include <testsuite_hooks.h> +#include <vector> + +#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S) +#define WIDEN(S) WIDEN_(_CharT, S) + +template<typename T, + template<typename, typename> class Formatter = std::range_formatter> +struct MyVector : std::vector<T> +{ + using std::vector<T>::vector; +}; + +template<typename T, + template<typename, typename> class Formatter, + typename CharT> +struct std::formatter<MyVector<T, Formatter>, 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 MyVector<T, Formatter>& mv, + std::basic_format_context<Out, CharT>& fc) const + { return _formatter.format(mv, fc); } + +private: + Formatter<T, CharT> _formatter; +}; + +template<typename _CharT, template<typename, typename> class Formatter> +void +test_default() +{ + MyVector<int, Formatter> vec{1, 2, 3}; + std::basic_string<_CharT> res; + + res = std::format(WIDEN("{}"), vec); + VERIFY( res == WIDEN("<1; 2; 3>") ); + res = std::format(WIDEN("{:}"), vec); + VERIFY( res == WIDEN("<1; 2; 3>") ); + res = std::format(WIDEN("{:n}"), vec); + VERIFY( res == WIDEN("1; 2; 3") ); + + res = std::format(WIDEN("{:3}"), vec); + VERIFY( res == WIDEN("<1; 2; 3>") ); + + res = std::format(WIDEN("{:10}"), vec); + VERIFY( res == WIDEN("<1; 2; 3> ") ); + + res = std::format(WIDEN("{:{}}"), vec, 10); + VERIFY( res == WIDEN("<1; 2; 3> ") ); + + res = std::format(WIDEN("{1:{0}}"), 10, vec); + VERIFY( res == WIDEN("<1; 2; 3> ") ); + + res = std::format(WIDEN("{:10n}"), vec); + VERIFY( res == WIDEN("1; 2; 3 ") ); + + res = std::format(WIDEN("{:*<11}"), vec); + VERIFY( res == WIDEN("<1; 2; 3>**") ); + + res = std::format(WIDEN("{:->12}"), vec); + VERIFY( res == WIDEN("---<1; 2; 3>") ); + + res = std::format(WIDEN("{:=^13}"), vec); + VERIFY( res == WIDEN("==<1; 2; 3>==") ); + + res = std::format(WIDEN("{:=^13n}"), vec); + VERIFY( res == WIDEN("===1; 2; 3===") ); + + res = std::format(WIDEN("{::#x}"), vec); + VERIFY( res == WIDEN("<0x1; 0x2; 0x3>") ); + + res = std::format(WIDEN("{:|^25n:#05x}"), vec); + VERIFY( res == WIDEN("|||0x001; 0x002; 0x003|||") ); + + // ':' is start of the format string for element + res = std::format(WIDEN("{::^+4}"), vec); + VERIFY( res == WIDEN("< +1 ; +2 ; +3 >") ); +} + +template<typename _CharT, template<typename, typename> class Formatter> +void +test_override() +{ + MyVector<_CharT, Formatter> vc{'a', 'b', 'c', 'd'}; + std::basic_string<_CharT> res; + + res = std::format(WIDEN("{:s}"), vc); + VERIFY( res == WIDEN("abcd") ); + res = std::format(WIDEN("{:?s}"), vc); + VERIFY( res == WIDEN("\"abcd\"") ); + res = std::format(WIDEN("{:+^6s}"), vc); + VERIFY( res == WIDEN("+abcd+") ); + + // TODO test map +} + +template<template<typename, typename> class Formatter> +void test_outputs() +{ + test_default<char, Formatter>(); + test_default<wchar_t, Formatter>(); + test_override<char, Formatter>(); + test_override<wchar_t, Formatter>(); +} + +void +test_nested() +{ + MyVector<MyVector<int>> v + { + {1, 2}, + {11, 12} + }; + + std::string res = std::format("{}", v); + VERIFY( res == "<<1; 2>; <11; 12>>" ); + + res = std::format("{:+^18:n:02}", v); + VERIFY( res == "+<01; 02; 11; 12>+" ); +} + +template<typename T, typename CharT> +using VectorFormatter = std::formatter<std::vector<T>, CharT>; + +int main() +{ + test_outputs<std::range_formatter>(); + test_outputs<VectorFormatter>(); + test_nested(); +} diff --git a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc new file mode 100644 index 00000000000..06574379ed5 --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc @@ -0,0 +1,190 @@ +// { dg-do run { target c++23 } } + +#include <format> +#include <list> +#include <span> +#include <testsuite_hooks.h> +#include <testsuite_iterators.h> +#include <vector> + +struct NotFormattable +{}; + +static_assert(!std::formattable<std::vector<NotFormattable>, char>); +static_assert(!std::formattable<std::span<NotFormattable>, 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; + } +} + +void +test_format_string() +{ + // invalid format spec 'p' + VERIFY( !is_range_formatter_spec_for("p", std::vector<int>()) ); + VERIFY( !is_format_string_for("{:p}", std::vector<int>()) ); + VERIFY( !is_range_formatter_spec_for("np", std::vector<int>()) ); + VERIFY( !is_format_string_for("{:np}", std::vector<int>()) ); + + // width needs to be integer type + VERIFY( !is_format_string_for("{:{}}", std::vector<int>(), 1.0f) ); + + // element format needs to be valid + VERIFY( !is_range_formatter_spec_for(":p", std::vector<int>()) ); + VERIFY( !is_format_string_for("{::p}", std::vector<int>()) ); + VERIFY( !is_range_formatter_spec_for("n:p", std::vector<int>()) ); + VERIFY( !is_format_string_for("{:n:p}", std::vector<int>()) ); +} + +#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S) +#define WIDEN(S) WIDEN_(_CharT, S) + +template<typename _CharT, typename Range> +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()); + }; + + std::basic_string<_CharT> res; + size_t size = 0; + + T v1[]{1, 2, 3}; + res = std::format(WIDEN("{}"), makeRange(v1)); + VERIFY( res == WIDEN("[1, 2, 3]") ); + res = std::format(WIDEN("{:}"), makeRange(v1)); + VERIFY( res == WIDEN("[1, 2, 3]") ); + res = std::format(WIDEN("{:n}"), makeRange(v1)); + VERIFY( res == WIDEN("1, 2, 3") ); + + res = std::format(WIDEN("{:3}"), makeRange(v1)); + VERIFY( res == WIDEN("[1, 2, 3]") ); + + res = std::format(WIDEN("{:10}"), makeRange(v1)); + VERIFY( res == WIDEN("[1, 2, 3] ") ); + + res = std::format(WIDEN("{:{}}"), makeRange(v1), 10); + VERIFY( res == WIDEN("[1, 2, 3] ") ); + + res = std::format(WIDEN("{1:{0}}"), 10, makeRange(v1)); + VERIFY( res == WIDEN("[1, 2, 3] ") ); + + res = std::format(WIDEN("{:10n}"), makeRange(v1)); + VERIFY( res == WIDEN("1, 2, 3 ") ); + + res = std::format(WIDEN("{:*<11}"), makeRange(v1)); + VERIFY( res == WIDEN("[1, 2, 3]**") ); + + res = std::format(WIDEN("{:->12}"), makeRange(v1)); + VERIFY( res == WIDEN("---[1, 2, 3]") ); + + res = std::format(WIDEN("{:=^13}"), makeRange(v1)); + VERIFY( res == WIDEN("==[1, 2, 3]==") ); + + res = std::format(WIDEN("{:=^13n}"), makeRange(v1)); + VERIFY( res == WIDEN("===1, 2, 3===") ); + + res = std::format(WIDEN("{::#x}"), makeRange(v1)); + VERIFY( res == WIDEN("[0x1, 0x2, 0x3]") ); + + res = std::format(WIDEN("{:|^25n:#05x}"), makeRange(v1)); + VERIFY( res == WIDEN("|||0x001, 0x002, 0x003|||") ); + + // ':' is start of the format string for element + res = std::format(WIDEN("{::^+04}"), makeRange(v1)); + VERIFY( res == WIDEN("[ +1 , +2 , +3 ]") ); + + size = std::formatted_size(WIDEN("{:}"), makeRange(v1)); + VERIFY( size == Sv(WIDEN("[1, 2, 3]")).size() ); + + size = std::formatted_size(WIDEN("{:3}"), makeRange(v1)); + VERIFY( size == Sv(WIDEN("[1, 2, 3]")).size() ); + + size = std::formatted_size(WIDEN("{:10}"), makeRange(v1)); + VERIFY( size == 10 ); + + size = std::formatted_size(WIDEN("{:|^25n:#05x}"), makeRange(v1)); + VERIFY( size == 25 ); +} + +template<typename Range> +void test_output_c() +{ + test_output<char, Range>(); + test_output<wchar_t, Range>(); +} + +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>>(); +} + +void +test_nested() +{ + std::vector<std::vector<int>> v + { + {1, 2}, + {11, 12} + }; + + std::string res = std::format("{}", v); + VERIFY( res == "[[1, 2], [11, 12]]" ); + + res = std::format("{:+^18:n:02}", v); + VERIFY( res == "+[01, 02, 11, 12]+" ); +} + +int main() +{ + test_format_string(); + test_outputs(); + test_nested(); +} diff --git a/libstdc++-v3/testsuite/std/format/ranges/string.cc b/libstdc++-v3/testsuite/std/format/ranges/string.cc new file mode 100644 index 00000000000..7f59f59dda3 --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/ranges/string.cc @@ -0,0 +1,226 @@ +// { dg-do run { target c++23 } } + +#include <format> +#include <span> +#include <testsuite_hooks.h> +#include <testsuite_iterators.h> +#include <vector> + +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 CharT value types are supported + VERIFY( !is_range_formatter_spec_for(L"s", std::vector<char>()) ); + VERIFY( !is_format_string_for(L"{:s}", std::vector<char>()) ); + VERIFY( !is_range_formatter_spec_for(L"s", std::vector<char>()) ); + VERIFY( !is_format_string_for(L"{:s}", std::vector<char>()) ); + VERIFY( !is_range_formatter_spec_for("s", std::vector<int>()) ); + VERIFY( !is_format_string_for("{:s}", std::vector<int>()) ); + + // invalid format stringss + VERIFY( !is_range_formatter_spec_for("?", std::vector<char>()) ); + VERIFY( !is_format_string_for("{:?}", std::vector<char>()) ); + VERIFY( !is_range_formatter_spec_for("ns", std::vector<char>()) ); + VERIFY( !is_format_string_for("{:ns}", std::vector<char>()) ); + VERIFY( !is_range_formatter_spec_for("s:", std::vector<char>()) ); + VERIFY( !is_format_string_for("{:s:}", std::vector<char>()) ); + + // precision is not supported, even for s + VERIFY( !is_range_formatter_spec_for(".10s", std::vector<char>()) ); + VERIFY( !is_format_string_for("{:.10s}", std::vector<char>()) ); + VERIFY( !is_format_string_for("{:.{}s}", std::vector<char>(), 10) ); + + // width needs to be integer type + VERIFY( !is_format_string_for("{:{}s}", std::vector<char>(), 1.0f) ); +} + +template<typename Range> +void test_output() +{ + using _CharT = std::ranges::range_value_t<Range>; + auto makeRange = [](std::basic_string<_CharT>& s) { + return Range(s.data(), s.data() + s.size()); + }; + std::basic_string<_CharT> res; + size_t size = 0; + + std::basic_string<_CharT> s1 = WIDEN("abcd"); + res = std::format(WIDEN("{}"), makeRange(s1)); + VERIFY( res == WIDEN("['a', 'b', 'c', 'd']") ); + + res = std::format(WIDEN("{::}"), makeRange(s1)); + VERIFY( res == WIDEN("[a, b, c, d]") ); + + res = std::format(WIDEN("{:s}"), makeRange(s1)); + VERIFY( res == WIDEN("abcd") ); + + res = std::format(WIDEN("{:?s}"), makeRange(s1)); + VERIFY( res == WIDEN(R"("abcd")") ); + + res = std::format(WIDEN("{:3s}"), makeRange(s1)); + VERIFY( res == WIDEN("abcd") ); + + res = std::format(WIDEN("{:7s}"), makeRange(s1)); + VERIFY( res == WIDEN("abcd ") ); + + res = std::format(WIDEN("{:{}s}"), makeRange(s1), 7); + VERIFY( res == WIDEN("abcd ") ); + + res = std::format(WIDEN("{1:{0}s}"), 7, makeRange(s1)); + VERIFY( res == WIDEN("abcd ") ); + + res = std::format(WIDEN("{:*>6s}"), makeRange(s1)); + VERIFY( res == WIDEN("**abcd") ); + + res = std::format(WIDEN("{:-<5s}"), makeRange(s1)); + VERIFY( res == WIDEN("abcd-") ); + + res = std::format(WIDEN("{:=^8s}"), makeRange(s1)); + VERIFY( res == WIDEN("==abcd==") ); + + std::basic_string<_CharT> s2(512, static_cast<_CharT>('a')); + res = std::format(WIDEN("{:=^8s}"), makeRange(s2)); + VERIFY( res == s2 ); + + size = std::formatted_size(WIDEN("{:s}"), makeRange(s1)); + VERIFY( size == 4 ); + + size = std::formatted_size(WIDEN("{:3s}"), makeRange(s1)); + VERIFY( size == 4 ); + + size = std::formatted_size(WIDEN("{:7s}"), makeRange(s1)); + VERIFY( size == 7 ); + + size = std::formatted_size(WIDEN("{:s}"), makeRange(s2)); + VERIFY( size == 512 ); +} + +template<typename CharT> +struct cstr_view +{ + cstr_view() = default; + explicit cstr_view(CharT* f, CharT* l) + : ptr(f) + { VERIFY(!*l); } + + struct sentinel + { + friend constexpr + bool operator==(CharT const* ptr, sentinel) noexcept + { return !*ptr; } + }; + + constexpr + CharT* begin() const noexcept + { return ptr; }; + static constexpr + sentinel end() noexcept + { return {}; } + +private: + CharT* ptr = ""; +}; + +template<typename CharT> +void +test_outputs() +{ + using namespace __gnu_test; + test_output<std::vector<CharT>>(); + test_output<std::span<CharT>>(); + test_output<cstr_view<CharT>>(); + + test_output<test_forward_range<CharT>>(); + test_output<test_forward_sized_range<CharT>>(); + + test_output<test_input_range<CharT>>(); + test_output<test_input_sized_range<CharT>>(); + + test_output<test_range_nocopy<CharT, input_iterator_wrapper_nocopy>>(); + test_output<test_sized_range<CharT, input_iterator_wrapper_nocopy>>(); + + test_output<std::span<const CharT>>(); + test_output<cstr_view<const CharT>>(); + test_output<test_forward_range<const CharT>>(); + + static_assert(!std::formattable<std::span<volatile CharT>, CharT>); + static_assert(!std::formattable<std::span<const volatile CharT>, CharT>); +} + +void +test_nested() +{ + std::string_view s1 = "str1"; + std::string_view s2 = "str2"; + + std::vector<std::string> vs; + vs.emplace_back(s1); + vs.emplace_back(s2); + + VERIFY( std::format("{}", vs) == R"(["str1", "str2"])" ); + VERIFY( std::format("{:}", vs) == R"(["str1", "str2"])" ); + VERIFY( std::format("{::?}", vs) == R"(["str1", "str2"])" ); + VERIFY( std::format("{::}", vs) == R"([str1, str2])" ); + + std::vector<std::vector<char>> vv; + vv.emplace_back(s1.begin(), s1.end()); + vv.emplace_back(s2.begin(), s2.end()); + std::string_view escaped = R"([['s', 't', 'r', '1'], ['s', 't', 'r', '2']])"; + + VERIFY( std::format("{}", vv) == escaped ); + VERIFY( std::format("{:}", vv) == escaped ); + VERIFY( std::format("{::}", vv) == escaped ); + VERIFY( std::format("{:::?}", vv) == escaped ); + VERIFY( std::format("{:::}", vv) == R"([[s, t, r, 1], [s, t, r, 2]])" ); + VERIFY( std::format("{::s}", vv) == R"([str1, str2])" ); + VERIFY( std::format("{::?s}", vv) == R"(["str1", "str2"])" ); +} + +int main() +{ + test_format_string(); + test_outputs<char>(); + test_outputs<wchar_t>(); + test_nested(); +} -- 2.49.0