aboutsummaryrefslogtreecommitdiff
path: root/libstdc++-v3/testsuite/std
diff options
context:
space:
mode:
authorTomasz Kamiński <tkaminsk@redhat.com>2025-03-28 09:30:22 +0100
committerTomasz Kamiński <tkaminsk@redhat.com>2025-04-15 17:09:59 +0200
commitf62e5d720de829cf346b799f3463fee53728ba6c (patch)
tree04752db00b2cc3c9aa1f60ba1fe9dcc7fa4e6213 /libstdc++-v3/testsuite/std
parenta039bab95750ead133e48672ab33378f513ba287 (diff)
downloadgcc-f62e5d720de829cf346b799f3463fee53728ba6c.zip
gcc-f62e5d720de829cf346b799f3463fee53728ba6c.tar.gz
gcc-f62e5d720de829cf346b799f3463fee53728ba6c.tar.bz2
libstdc++: Implement formatter for ranges and range_formatter [PR109162]
This patch implements formatter specialization for input_ranges and range_formatter class from P2286R8, as adjusted by P2585R1. The formatter for pair/tuple is not yet provided, making maps not formattable. This introduces an new _M_format_range member to internal __formatter_str, that formats range as _CharT as string, according to the format spec. This function transform any contiguous range into basic_string_view directly, 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 its 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 retrieve any format argument from format context, visit and use handle to format it. As basic_format_context provide no user-facing constructor, the user are not able to construct object of that type with arbitrary iterators. The signatures of the user-facing parse and format methods 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. These 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. Reviewed-by: Jonathan Wakely <jwakely@redhat.com> Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
Diffstat (limited to 'libstdc++-v3/testsuite/std')
-rw-r--r--libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc4
-rw-r--r--libstdc++-v3/testsuite/std/format/formatter/requirements.cc14
-rw-r--r--libstdc++-v3/testsuite/std/format/ranges/format_kind.cc94
-rw-r--r--libstdc++-v3/testsuite/std/format/ranges/formatter.cc145
-rw-r--r--libstdc++-v3/testsuite/std/format/ranges/sequence.cc190
-rw-r--r--libstdc++-v3/testsuite/std/format/ranges/string.cc226
6 files changed, 665 insertions, 8 deletions
diff --git a/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc b/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc
index ff5f075..1f3edc9 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 416b9a8..1c9a0a5 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-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()
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 0000000..14b9ff2
--- /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 0000000..2045b51
--- /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 0000000..0657437
--- /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 0000000..7f59f59
--- /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();
+}