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 <jwakely@redhat.com> Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
This commit is contained in:
parent
5a48e7732d
commit
473dde5252
6 changed files with 811 additions and 91 deletions
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
209
libstdc++-v3/testsuite/std/format/ranges/map.cc
Normal file
209
libstdc++-v3/testsuite/std/format/ranges/map.cc
Normal file
|
@ -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();
|
||||
}
|
|
@ -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_cont<std::vector<int>>();
|
||||
test_output_cont<std::list<int>>();
|
||||
test_output_cont<std::array<int, 3>>();
|
||||
|
||||
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_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_c<std::span<const int>>();
|
||||
test_output_c<test_forward_range<const 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
|
||||
|
|
259
libstdc++-v3/testsuite/std/format/tuple.cc
Normal file
259
libstdc++-v3/testsuite/std/format/tuple.cc
Normal file
|
@ -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();
|
||||
}
|
|
@ -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>;
|
||||
|
|
Loading…
Add table
Reference in a new issue