From f62e5d720de829cf346b799f3463fee53728ba6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Kami=C5=84ski?= Date: Fri, 28 Mar 2025 09:30:22 +0100 Subject: libstdc++: Implement formatter for ranges and range_formatter [PR109162] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 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 Signed-off-by: Tomasz KamiƄski --- .../testsuite/std/format/formatter/lwg3944.cc | 4 +- .../testsuite/std/format/formatter/requirements.cc | 14 +- .../testsuite/std/format/ranges/format_kind.cc | 94 +++++++++ .../testsuite/std/format/ranges/formatter.cc | 145 +++++++++++++ .../testsuite/std/format/ranges/sequence.cc | 190 +++++++++++++++++ libstdc++-v3/testsuite/std/format/ranges/string.cc | 226 +++++++++++++++++++++ 6 files changed, 665 insertions(+), 8 deletions(-) create mode 100644 libstdc++-v3/testsuite/std/format/ranges/format_kind.cc create mode 100644 libstdc++-v3/testsuite/std/format/ranges/formatter.cc create mode 100644 libstdc++-v3/testsuite/std/format/ranges/sequence.cc create mode 100644 libstdc++-v3/testsuite/std/format/ranges/string.cc (limited to 'libstdc++-v3/testsuite/std') 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 +#include 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 struct formatter - using Farr = std::format_context::formatter_type; - static_assert( ! std::is_default_constructible_v ); - static_assert( ! std::is_copy_constructible_v ); - static_assert( ! std::is_move_constructible_v ); - static_assert( ! std::is_copy_assignable_v ); - static_assert( ! std::is_move_assignable_v ); + // 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; + // static_assert( ! std::is_default_constructible_v ); + // static_assert( ! std::is_copy_constructible_v ); + // static_assert( ! std::is_move_constructible_v ); + // static_assert( ! std::is_copy_assignable_v ); + // static_assert( ! std::is_move_assignable_v ); } 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static_assert( std::format_kind> == std::range_format::sequence ); +static_assert( std::format_kind> == std::range_format::sequence ); +static_assert( std::format_kind> == std::range_format::sequence ); + +static_assert( std::format_kind> == std::range_format::set ); +static_assert( std::format_kind> == std::range_format::set ); +static_assert( std::format_kind> == std::range_format::set ); +static_assert( std::format_kind> == std::range_format::set ); +static_assert( std::format_kind> == std::range_format::set ); +static_assert( std::format_kind> == std::range_format::set ); + +static_assert( std::format_kind> == std::range_format::map ); +static_assert( std::format_kind> == std::range_format::map ); +static_assert( std::format_kind> == std::range_format::map ); +static_assert( std::format_kind> == std::range_format::map ); +static_assert( std::format_kind> == std::range_format::map ); +static_assert( std::format_kind> == std::range_format::map ); + +template +struct MyVec : std::vector +{}; + +static_assert( std::format_kind> == std::range_format::sequence ); + +template +struct MySet : std::vector +{ + using key_type = T; +}; + +static_assert( std::format_kind> == std::range_format::set ); + +template +struct MyMap : std::vector +{ + using key_type = T; + using mapped_type = int; +}; + +static_assert( std::format_kind>> == std::range_format::map ); +static_assert( std::format_kind>> == std::range_format::map ); +static_assert( std::format_kind> == std::range_format::set ); + +template +struct CustFormat : std::vector +{ + using std::vector::vector; +}; + +template +constexpr auto std::format_kind> = rf; + +void test_override() +{ + CustFormat disabledf; + static_assert( !std::formattable ); + + CustFormat seqf{1, 2, 3}; + VERIFY( std::format("{}", seqf) == "[1, 2, 3]" ); + + CustFormat setf{1, 2, 3}; + VERIFY( std::format("{}", setf) == "{1, 2, 3}" ); + + // TODO test map once formatter for pair is implenented + + CustFormat stringf{'a', 'b', 'c', 'd'}; + VERIFY( std::format("{}", stringf) == "abcd" ); + // Support precision as string do + VERIFY( std::format("{:.2}", stringf) == "ab" ); + + CustFormat 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 +#include +#include + +#define WIDEN_(C, S) ::std::__format::_Widen(S, L##S) +#define WIDEN(S) WIDEN_(_CharT, S) + +template class Formatter = std::range_formatter> +struct MyVector : std::vector +{ + using std::vector::vector; +}; + +template class Formatter, + typename CharT> +struct std::formatter, CharT> +{ + constexpr formatter() noexcept + { + using _CharT = CharT; + _formatter.set_brackets(WIDEN("<"), WIDEN(">")); + _formatter.set_separator(WIDEN("; ")); + } + + constexpr std::basic_format_parse_context::iterator + parse(std::basic_format_parse_context& pc) + { return _formatter.parse(pc); } + + template + typename std::basic_format_context::iterator + format(const MyVector& mv, + std::basic_format_context& fc) const + { return _formatter.format(mv, fc); } + +private: + Formatter _formatter; +}; + +template class Formatter> +void +test_default() +{ + MyVector 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 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 class Formatter> +void test_outputs() +{ + test_default(); + test_default(); + test_override(); + test_override(); +} + +void +test_nested() +{ + MyVector> 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 +using VectorFormatter = std::formatter, CharT>; + +int main() +{ + test_outputs(); + test_outputs(); + 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 +#include +#include +#include +#include +#include + +struct NotFormattable +{}; + +static_assert(!std::formattable, char>); +static_assert(!std::formattable, wchar_t>); + +template +bool +is_format_string_for(const char* str, Args&&... args) +{ + try { + (void) std::vformat(str, std::make_format_args(args...)); + return true; + } catch (const std::format_error&) { + return false; + } +} + +template +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 +bool is_range_formatter_spec_for(CharT const* spec, Rg&& rg) +{ + using V = std::remove_cvref_t>; + std::range_formatter fmt; + std::basic_format_parse_context 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()) ); + VERIFY( !is_format_string_for("{:p}", std::vector()) ); + VERIFY( !is_range_formatter_spec_for("np", std::vector()) ); + VERIFY( !is_format_string_for("{:np}", std::vector()) ); + + // width needs to be integer type + VERIFY( !is_format_string_for("{:{}}", std::vector(), 1.0f) ); + + // element format needs to be valid + VERIFY( !is_range_formatter_spec_for(":p", std::vector()) ); + VERIFY( !is_format_string_for("{::p}", std::vector()) ); + VERIFY( !is_range_formatter_spec_for("n:p", std::vector()) ); + VERIFY( !is_format_string_for("{:n:p}", std::vector()) ); +} + +#define WIDEN_(C, S) ::std::__format::_Widen(S, L##S) +#define WIDEN(S) WIDEN_(_CharT, S) + +template +void test_output() +{ + using Sv = std::basic_string_view<_CharT>; + using T = std::ranges::range_value_t; + auto makeRange = [](std::span 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 +void test_output_c() +{ + test_output(); + test_output(); +} + +void +test_outputs() +{ + using namespace __gnu_test; + test_output_c>(); + test_output_c>(); + test_output_c>(); + + test_output_c>(); + test_output_c>(); + test_output_c>(); + + test_output_c>(); + test_output_c>(); +} + +void +test_nested() +{ + std::vector> 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 +#include +#include +#include +#include + +template +bool +is_format_string_for(const char* str, Args&&... args) +{ + try { + (void) std::vformat(str, std::make_format_args(args...)); + return true; + } catch (const std::format_error&) { + return false; + } +} + +template +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 +bool is_range_formatter_spec_for(CharT const* spec, Rg&& rg) +{ + using V = std::remove_cvref_t>; + std::range_formatter fmt; + std::basic_format_parse_context pc(spec); + try { + (void)fmt.parse(pc); + return true; + } catch (const std::format_error&) { + return false; + } +} + +#define WIDEN_(C, S) ::std::__format::_Widen(S, L##S) +#define WIDEN(S) WIDEN_(_CharT, S) + +void +test_format_string() +{ + // only CharT value types are supported + VERIFY( !is_range_formatter_spec_for(L"s", std::vector()) ); + VERIFY( !is_format_string_for(L"{:s}", std::vector()) ); + VERIFY( !is_range_formatter_spec_for(L"s", std::vector()) ); + VERIFY( !is_format_string_for(L"{:s}", std::vector()) ); + VERIFY( !is_range_formatter_spec_for("s", std::vector()) ); + VERIFY( !is_format_string_for("{:s}", std::vector()) ); + + // invalid format stringss + VERIFY( !is_range_formatter_spec_for("?", std::vector()) ); + VERIFY( !is_format_string_for("{:?}", std::vector()) ); + VERIFY( !is_range_formatter_spec_for("ns", std::vector()) ); + VERIFY( !is_format_string_for("{:ns}", std::vector()) ); + VERIFY( !is_range_formatter_spec_for("s:", std::vector()) ); + VERIFY( !is_format_string_for("{:s:}", std::vector()) ); + + // precision is not supported, even for s + VERIFY( !is_range_formatter_spec_for(".10s", std::vector()) ); + VERIFY( !is_format_string_for("{:.10s}", std::vector()) ); + VERIFY( !is_format_string_for("{:.{}s}", std::vector(), 10) ); + + // width needs to be integer type + VERIFY( !is_format_string_for("{:{}s}", std::vector(), 1.0f) ); +} + +template +void test_output() +{ + using _CharT = std::ranges::range_value_t; + 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 +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 +void +test_outputs() +{ + using namespace __gnu_test; + test_output>(); + test_output>(); + test_output>(); + + test_output>(); + test_output>(); + + test_output>(); + test_output>(); + + test_output>(); + test_output>(); + + test_output>(); + test_output>(); + test_output>(); + + static_assert(!std::formattable, CharT>); + static_assert(!std::formattable, CharT>); +} + +void +test_nested() +{ + std::string_view s1 = "str1"; + std::string_view s2 = "str2"; + + std::vector 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> 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(); + test_outputs(); + test_nested(); +} -- cgit v1.1