@@ -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, typename _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,346 @@ 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<typename _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() || *__first != 's')
+ __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 (__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, typename _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 iterator.
+ 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, typename _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, 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);
+ _String_view _M_sep = _Seps::_S_comma();
+ formatter<_Tp, _CharT> _M_fval;
+ };
+
+ // In standard this is shown as inheriting 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
@@ -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>
@@ -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()
@@ -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
@@ -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-unqual 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()
new file mode 100644
@@ -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();
+}
new file mode 100644
@@ -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();
+}
new file mode 100644
@@ -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();
+}
new file mode 100644
@@ -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();
+}