From ffd65a3c564f4acdd8c8d546a8362798081d62e4 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Wed, 10 Jul 2024 23:35:48 +0800 Subject: [PATCH 1/7] Implement `formatter` specializations for container adaptors --- stl/inc/format | 12 + stl/inc/queue | 52 + stl/inc/stack | 26 + stl/inc/yvals_core.h | 3 + tests/libcxx/expected_results.txt | 9 +- tests/std/test.lst | 1 + .../env.lst | 4 + .../test.cpp | 1206 +++++++++++++++++ .../test.compile.pass.cpp | 6 + 9 files changed, 1311 insertions(+), 8 deletions(-) create mode 100644 tests/std/tests/P2286R8_text_formatting_container_adaptors/env.lst create mode 100644 tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp diff --git a/stl/inc/format b/stl/inc/format index be7a99523d..4f095cc2ea 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -4774,6 +4774,18 @@ public: private: _Fill_align_and_width_specs<_CharT> _Specs; }; + +template +consteval auto _Get_member_pointer_to_adapted_container() noexcept { + struct _Container_exposer : _Adaptor { + using _Adaptor::c; + + _Container_exposer(const _Container_exposer&) = delete; + _Container_exposer& operator=(const _Container_exposer&) = delete; + ~_Container_exposer() = delete; + }; + return &_Container_exposer::c; +} #endif // _HAS_CXX23 _STD_END diff --git a/stl/inc/queue b/stl/inc/queue index b61ce25002..271e2083b8 100644 --- a/stl/inc/queue +++ b/stl/inc/queue @@ -18,6 +18,7 @@ #if _HAS_CXX23 #include <__msvc_ranges_to.hpp> +#include #include #endif // _HAS_CXX23 @@ -223,6 +224,31 @@ void swap(queue<_Ty, _Container>& _Left, queue<_Ty, _Container>& _Right) noexcep template struct uses_allocator, _Alloc> : uses_allocator<_Container, _Alloc>::type {}; +#if _HAS_CXX23 +// Per LWG-3997, `_CharT` in library-provided `formatter` specializations is +// constrained to character types supported by `format`. +template <_Format_supported_charT _CharT, class _Ty, formattable<_CharT> _Container> +struct formatter, _CharT> { +private: + using _Maybe_const_container = _Fmt_maybe_const<_Container, _CharT>; + using _Maybe_const_adaptor = _Maybe_const, queue<_Ty, _Container>>; + + formatter<_RANGES ref_view<_Maybe_const_container>, _CharT> _Underlying; + +public: + template + constexpr _ParseCtx::iterator parse(_ParseCtx& _Ctx) { + return _Underlying.parse(_Ctx); + } + + template + _FmtCtx::iterator format(_Maybe_const_adaptor& _Adaptor, _FmtCtx& _Ctx) const { + constexpr auto _Mem_cont_ptr = _STD _Get_member_pointer_to_adapted_container>(); + return _Underlying.format(_Adaptor.*_Mem_cont_ptr, _Ctx); + } +}; +#endif // _HAS_CXX23 + _EXPORT_STD template , class _Pr = less> class priority_queue { public: @@ -490,6 +516,32 @@ void swap(priority_queue<_Ty, _Container, _Pr>& _Left, priority_queue<_Ty, _Cont template struct uses_allocator, _Alloc> : uses_allocator<_Container, _Alloc>::type {}; +#if _HAS_CXX23 +// Per LWG-3997, `_CharT` in library-provided `formatter` specializations is +// constrained to character types supported by `format`. +template <_Format_supported_charT _CharT, class _Ty, formattable<_CharT> _Container, class _Comp> +struct formatter, _CharT> { +private: + using _Maybe_const_container = _Fmt_maybe_const<_Container, _CharT>; + using _Maybe_const_adaptor = + _Maybe_const, priority_queue<_Ty, _Container, _Comp>>; + + formatter<_RANGES ref_view<_Maybe_const_container>, _CharT> _Underlying; + +public: + template + constexpr _ParseCtx::iterator parse(_ParseCtx& _Ctx) { + return _Underlying.parse(_Ctx); + } + + template + _FmtCtx::iterator format(_Maybe_const_adaptor& _Adaptor, _FmtCtx& _Ctx) const { + constexpr auto _Mem_cont_ptr = + _STD _Get_member_pointer_to_adapted_container>(); + return _Underlying.format(_Adaptor.*_Mem_cont_ptr, _Ctx); + } +}; +#endif // _HAS_CXX23 _STD_END #pragma pop_macro("new") diff --git a/stl/inc/stack b/stl/inc/stack index e98e69dee4..ba3635c1d4 100644 --- a/stl/inc/stack +++ b/stl/inc/stack @@ -11,6 +11,7 @@ #if _HAS_CXX23 #include <__msvc_ranges_to.hpp> +#include #include #endif // _HAS_CXX23 @@ -209,6 +210,31 @@ void swap(stack<_Ty, _Container>& _Left, stack<_Ty, _Container>& _Right) noexcep template struct uses_allocator, _Alloc> : uses_allocator<_Container, _Alloc>::type {}; + +#if _HAS_CXX23 +// Per LWG-3997, `_CharT` in library-provided `formatter` specializations is +// constrained to character types supported by `format`. +template <_Format_supported_charT _CharT, class _Ty, formattable<_CharT> _Container> +struct formatter, _CharT> { +private: + using _Maybe_const_container = _Fmt_maybe_const<_Container, _CharT>; + using _Maybe_const_adaptor = _Maybe_const, stack<_Ty, _Container>>; + + formatter<_RANGES ref_view<_Maybe_const_container>, _CharT> _Underlying; + +public: + template + constexpr _ParseCtx::iterator parse(_ParseCtx& _Ctx) { + return _Underlying.parse(_Ctx); + } + + template + _FmtCtx::iterator format(_Maybe_const_adaptor& _Adaptor, _FmtCtx& _Ctx) const { + constexpr auto _Mem_cont_ptr = _STD _Get_member_pointer_to_adapted_container>(); + return _Underlying.format(_Adaptor.*_Mem_cont_ptr, _Ctx); + } +}; +#endif // _HAS_CXX23 _STD_END #pragma pop_macro("new") diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 94fb2b4573..2a72105b68 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -356,6 +356,7 @@ // P2165R4 Compatibility Between tuple, pair, And tuple-like Objects // P2166R1 Prohibiting basic_string And basic_string_view Construction From nullptr // P2186R2 Removing Garbage Collection Support +// P2286R8 Formatting Ranges // P2273R3 constexpr unique_ptr // P2278R4 cbegin Should Always Return A Constant Iterator // P2286R8 Formatting Ranges @@ -383,6 +384,7 @@ // P2539R4 Synchronizing print() With The Underlying Stream // P2540R1 Empty Product For Certain Views // P2549R1 unexpected::error() +// P2585R1 Improve Default Container Formatting // P2599R2 mdspan: index_type, size_type // P2604R0 mdspan: data_handle_type, data_handle(), exhaustive // P2613R1 mdspan: empty() @@ -1755,6 +1757,7 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect #define __cpp_lib_constexpr_typeinfo 202106L #define __cpp_lib_containers_ranges 202202L #define __cpp_lib_expected 202211L +#define __cpp_lib_format_ranges 202207L #define __cpp_lib_formatters 202302L #define __cpp_lib_forward_like 202207L #define __cpp_lib_freestanding_expected 202311L diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index fa31a5e156..5fe8e83667 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -238,14 +238,6 @@ std/language.support/support.limits/support.limits.general/cstdlib.version.compi # P2255R2 "Type Traits To Detect References Binding To Temporaries" std/language.support/support.limits/support.limits.general/type_traits.version.compile.pass.cpp FAIL -# P2286R8 Formatting Ranges -std/containers/container.adaptors/container.adaptors.format/format.functions.format.pass.cpp FAIL -std/containers/container.adaptors/container.adaptors.format/format.functions.vformat.pass.cpp FAIL -std/containers/container.adaptors/container.adaptors.format/format.pass.cpp FAIL -std/containers/container.adaptors/container.adaptors.format/parse.pass.cpp FAIL -std/containers/container.adaptors/container.adaptors.format/types.compile.pass.cpp FAIL -std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp FAIL - # *** MISSING COMPILER FEATURES *** # P1169R4 static operator() @@ -968,6 +960,7 @@ std/input.output/string.streams/stringstream/stringstream.members/str.allocator_ # These formatter tests need to create basic_format_contexts, so test_format_context.h # says "Please create a vendor specific version of the test functions". # If we do, some of these tests should be able to work. +std/containers/container.adaptors/container.adaptors.format/format.pass.cpp FAIL std/containers/sequences/vector.bool/vector.bool.fmt/format.pass.cpp FAIL std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp FAIL std/utilities/format/format.formatter/format.context/format.context/advance_to.pass.cpp FAIL diff --git a/tests/std/test.lst b/tests/std/test.lst index 4631d566e5..d6a336890f 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -614,6 +614,7 @@ tests\P2278R4_const_span tests\P2278R4_ranges_const_iterator_machinery tests\P2278R4_ranges_const_range_machinery tests\P2278R4_views_as_const +tests\P2286R8_text_formatting_container_adaptors tests\P2286R8_text_formatting_debug_enabled_specializations tests\P2286R8_text_formatting_escaping tests\P2286R8_text_formatting_escaping_legacy_text_encoding diff --git a/tests/std/tests/P2286R8_text_formatting_container_adaptors/env.lst b/tests/std/tests/P2286R8_text_formatting_container_adaptors/env.lst new file mode 100644 index 0000000000..642f530ffa --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_container_adaptors/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp b/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp new file mode 100644 index 0000000000..9070ec6617 --- /dev/null +++ b/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp @@ -0,0 +1,1206 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// derived from libc++'s test files: +// * support/format.functions.common.h +// * std/containers/container.adaptors/container.adaptors.format/format.functions.tests.h +// * std/containers/container.adaptors/container.adaptors.format/format.functions.format.pass.cpp +// * std/containers/container.adaptors/container.adaptors.format/format.functions.vformat.pass.cpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef _DEBUG +#define DEFAULT_IDL_SETTING 2 +#else +#define DEFAULT_IDL_SETTING 0 +#endif + +using namespace std; + +#define CSTR(Str) TYPED_LITERAL(CharT, Str) +#define STR(Str) basic_string(CSTR(Str)) +#define SV(Str) basic_string_view(CSTR(Str)) + +namespace detail { + consteval string_view get_format_types() noexcept { + return "aAbBcdeEfFgGopPsxX?"; + } + + template + basic_string get_colons() { + return basic_string(N, CharT(':')); + } + + template + vector> fmt_invalid_types(string_view valid) { + vector> result; + if constexpr (is_permissive_v) { + for (const char c : get_format_types()) { + if (valid.find(c) == string_view::npos) { + result.push_back(format(SV("{{{}{}}}"), get_colons(), c)); + } + } + } else { + // ranges::to is not available in C++20. + ranges::copy(get_format_types() | views::filter([&](char type) { + return valid.find(type) == string_view::npos; + }) | views::transform([&](char type) { return format(SV("{{{}{}}}"), get_colons(), type); }), + back_inserter(result)); + } + return result; + } +} // namespace detail + +// Creates format string for the invalid types. +// +// valid contains a list of types that are valid. +// +// The return value is a collection of basic_strings, instead of +// basic_string_views since the values are temporaries. +template +vector> fmt_invalid_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +// Like fmt_invalid_types but when the format spec is for an underlying formatter. +template +vector> fmt_invalid_nested_types(string_view valid) { + return detail::fmt_invalid_types(valid); +} + +template +struct context {}; + +template <> +struct context { + using type = format_context; +}; + +template <> +struct context { + using type = wformat_context; +}; + +template +using context_t = context::type; + +// A user-defined type used to test the handle formatter. +enum class status : uint16_t { foo = 0xAAAA, bar = 0x5555, foobar = 0xAA55 }; + +// The formatter for a user-defined type used to test the handle formatter. +template +struct formatter { + // During the 2023 Issaquah meeting LEWG made it clear a formatter is + // required to call its parse function. LWG3892 Adds the wording for that + // requirement. Therefore this formatter is initialized in an invalid state. + // A call to parse sets it in a valid state and a call to format validates + // the state. + int type = -1; + + constexpr auto parse(basic_format_parse_context& parse_ctx) -> decltype(parse_ctx.begin()) { + auto begin = parse_ctx.begin(); + auto end = parse_ctx.end(); + type = 0; + if (begin == end) { + return begin; + } + + switch (*begin) { + case CharT('x'): + break; + case CharT('X'): + type = 1; + break; + case CharT('s'): + type = 2; + break; + case CharT('}'): + return begin; + default: + throw_format_error("The type option contains an invalid value for a status formatting argument"); + } + + ++begin; + if (begin != end && *begin != CharT('}')) { + throw_format_error("The format specifier should consume the input or end with a '}'"); + } + + return begin; + } + + template + auto format(status s, basic_format_context& ctx) const -> decltype(ctx.out()) { + const char* names[] = {"foo", "bar", "foobar"}; + char buffer[7]; + const char* pbegin = names[0]; + const char* pend = names[0]; + switch (type) { + case -1: + throw_format_error("The formatter's parse function has not been called."); + + case 0: + pbegin = buffer; + buffer[0] = '0'; + buffer[1] = 'x'; + pend = to_chars(&buffer[2], end(buffer), static_cast(s), 16).ptr; + buffer[6] = '\0'; + break; + + case 1: + pbegin = buffer; + buffer[0] = '0'; + buffer[1] = 'X'; + pend = to_chars(&buffer[2], end(buffer), static_cast(s), 16).ptr; + transform(static_cast(&buffer[2]), pend, &buffer[2], + [](char c) { return static_cast(toupper(c)); }); + buffer[6] = '\0'; + break; + + case 2: + switch (s) { + case status::foo: + pbegin = names[0]; + break; + case status::bar: + pbegin = names[1]; + break; + case status::foobar: + pbegin = names[2]; + break; + } + pend = pbegin + strlen(pbegin); + break; + } + +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(push) +#pragma warning(disable : 4365) +#endif // !defined(__clang__) && !defined(__EDG__) + return copy(pbegin, pend, ctx.out()); +#if !defined(__clang__) && !defined(__EDG__) +#pragma warning(pop) +#endif // !defined(__clang__) && !defined(__EDG__) + } + +private: + [[noreturn]] void throw_format_error(const char* s) const { + throw format_error(s); + } +}; + +// +// Char +// + +template +void test_char_default(TestFunction check, ExceptionTest check_exception, auto&& input) { + // Note when no range-underlying-spec is present the char is escaped, + check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{}"), input); + check(SV("['H', 'e', 'l', 'l', 'o']^42"), SV("{}^42"), input); + check(SV("['H', 'e', 'l', 'l', 'o']^42"), SV("{:}^42"), input); + + // when one is present there is no escaping, + check(SV("[H, e, l, l, o]"), SV("{::}"), input); + check(SV("[H, e, l, l, o]"), SV("{::<}"), input); + // unless forced by the type specifier. + check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{::?}"), input); + check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{::30}"), input); + + check(SV("['H', 'e', 'l', 'l', 'o'] "), SV("{:{}}"), input, 30); + check(SV("['H', 'e', 'l', 'l', 'o']*****"), SV("{:*<{}}"), input, 30); + check(SV("__['H', 'e', 'l', 'l', 'o']___"), SV("{:_^{}}"), input, 30); + check(SV("#####['H', 'e', 'l', 'l', 'o']"), SV("{:#>{}}"), input, 30); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__'H', 'e', 'l', 'l', 'o'___"), SV("{:_^28n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[H , e , l , l , o ]"), SV("{::4}"), input); + check(SV("[H***, e***, l***, l***, o***]"), SV("{::*<4}"), input); + check(SV("[_H__, _e__, _l__, _l__, _o__]"), SV("{::_^4}"), input); + check(SV("[:::H, :::e, :::l, :::l, :::o]"), SV("{:::>4}"), input); + + check(SV("[H , e , l , l , o ]"), SV("{::{}}"), input, 4); + check(SV("[H***, e***, l***, l***, o***]"), SV("{::*<{}}"), input, 4); + check(SV("[_H__, _e__, _l__, _l__, _o__]"), SV("{::_^{}}"), input, 4); + check(SV("[:::H, :::e, :::l, :::l, :::o]"), SV("{:::>{}}"), input, 4); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier for a character does not allow the sign option", SV("{::-}"), input); + check_exception("The format specifier for a character does not allow the sign option", SV("{::+}"), input); + check_exception("The format specifier for a character does not allow the sign option", SV("{:: }"), input); + + check(SV("[72, 101, 108, 108, 111]"), SV("{::-d}"), input); + check(SV("[+72, +101, +108, +108, +111]"), SV("{::+d}"), input); + check(SV("[ 72, 101, 108, 108, 111]"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception( + "The format specifier for a character does not allow the alternate form option", SV("{::#}"), input); + + check(SV("[0x48, 0x65, 0x6c, 0x6c, 0x6f]"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("The format specifier for a character does not allow the zero-padding option", SV("{::05}"), input); + + check(SV("[00110, 00145, 00154, 00154, 00157]"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[H, e, l, l, o]"), SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBcdoxX?")) { + check_exception("The type option contains an invalid value for a character formatting argument", fmt, input); + } + + // ***** Both have a format specifier + check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^25::>2}"), input); + check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^{}::>2}"), input, 25); + check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^{}::>{}}"), input, 25, 2); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>2}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 25); +} + +template +void test_char_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("Hello"), SV("{:s}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("Hello "), SV("{:8s}"), input); + check(SV("Hello***"), SV("{:*<8s}"), input); + check(SV("_Hello__"), SV("{:_^8s}"), input); + check(SV("###Hello"), SV("{:#>8s}"), input); + + check(SV("Hello "), SV("{:{}s}"), input, 8); + check(SV("Hello***"), SV("{:*<{}s}"), input, 8); + check(SV("_Hello__"), SV("{:_^{}s}"), input, 8); + check(SV("###Hello"), SV("{:#>{}s}"), input, 8); + + check_exception("The format string contains an invalid escape sequence", SV("{:} fmt : fmt_invalid_nested_types("bBcdoxX?")) { + check_exception("The type option contains an invalid value for a character formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check_exception("Type s and an underlying format specification can't be used together", SV("{:5s:5}"), input); +} + +template +void test_char_escaped_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV(R"("Hello")"), SV("{:?s}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"("Hello" )"), SV("{:10?s}"), input); + check(SV(R"("Hello"***)"), SV("{:*<10?s}"), input); + check(SV(R"(_"Hello"__)"), SV("{:_^10?s}"), input); + check(SV(R"(###"Hello")"), SV("{:#>10?s}"), input); + + check(SV(R"("Hello" )"), SV("{:{}?s}"), input, 10); + check(SV(R"("Hello"***)"), SV("{:*<{}?s}"), input, 10); + check(SV(R"(_"Hello"__)"), SV("{:_^{}?s}"), input, 10); + check(SV(R"(###"Hello")"), SV("{:#>{}?s}"), input, 10); + + check_exception("The format string contains an invalid escape sequence", SV("{:} +void test_char(TestFunction check, ExceptionTest check_exception) { + // These values are in numeric order when using ASCII, which is used by the priority_queue. + array input{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}; + test_char_default(check, check_exception, queue{input.begin(), input.end()}); + test_char_default(check, check_exception, priority_queue{input.begin(), input.end(), greater{}}); + test_char_default(check, check_exception, stack{input.begin(), input.end()}); + + test_char_string(check, check_exception, queue{input.begin(), input.end()}); + test_char_string(check, check_exception, priority_queue{input.begin(), input.end(), greater{}}); + test_char_string(check, check_exception, stack{input.begin(), input.end()}); + + test_char_escaped_string(check, check_exception, queue{input.begin(), input.end()}); + test_char_escaped_string(check, check_exception, priority_queue{input.begin(), input.end(), greater{}}); + test_char_escaped_string(check, check_exception, stack{input.begin(), input.end()}); + + // LWG3881 fixes formatting container adaptors backed by a string. + test_char_default(check, check_exception, queue{basic_string{input.begin(), input.end()}}); + test_char_default( + check, check_exception, priority_queue{greater{}, basic_string{input.begin(), input.end()}}); + test_char_default(check, check_exception, stack{basic_string{input.begin(), input.end()}}); +} + +// +// char -> wchar_t +// + +template +void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { + array input{'H', 'e', 'l', 'l', 'o'}; + test_char_default(check, check_exception, queue{input.begin(), input.end()}); + test_char_default(check, check_exception, priority_queue{input.begin(), input.end(), greater{}}); + test_char_default(check, check_exception, stack{input.begin(), input.end()}); + + // The types s and ?s may only be used when using range_formatter + // where the types T and charT are the same. This means this can't be used for + // range_formatter even when formatter has a + // debug-enabled specialization. + + using CharT = wchar_t; + check_exception( + "Type s requires character type as formatting argument", SV("{:s}"), queue{input.begin(), input.end()}); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), + priority_queue{input.begin(), input.end()}); + check_exception( + "Type s requires character type as formatting argument", SV("{:s}"), stack{input.begin(), input.end()}); + check_exception( + "Type ?s requires character type as formatting argument", SV("{:?s}"), queue{input.begin(), input.end()}); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), + priority_queue{input.begin(), input.end()}); + check_exception( + "Type ?s requires character type as formatting argument", SV("{:?s}"), stack{input.begin(), input.end()}); +} + +// +// Bool +// + +template +void test_bool(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[true, true, false]"), SV("{}"), input); + check(SV("[true, true, false]^42"), SV("{}^42"), input); + check(SV("[true, true, false]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[true, true, false] "), SV("{:24}"), input); + check(SV("[true, true, false]*****"), SV("{:*<24}"), input); + check(SV("__[true, true, false]___"), SV("{:_^24}"), input); + check(SV("#####[true, true, false]"), SV("{:#>24}"), input); + + check(SV("[true, true, false] "), SV("{:{}}"), input, 24); + check(SV("[true, true, false]*****"), SV("{:*<{}}"), input, 24); + check(SV("__[true, true, false]___"), SV("{:_^{}}"), input, 24); + check(SV("#####[true, true, false]"), SV("{:#>{}}"), input, 24); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__true, true, false___"), SV("{:_^22n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[true , true , false ]"), SV("{::7}"), input); + check(SV("[true***, true***, false**]"), SV("{::*<7}"), input); + check(SV("[_true__, _true__, _false_]"), SV("{::_^7}"), input); + check(SV("[:::true, :::true, ::false]"), SV("{:::>7}"), input); + + check(SV("[true , true , false ]"), SV("{::{}}"), input, 7); + check(SV("[true***, true***, false**]"), SV("{::*<{}}"), input, 7); + check(SV("[_true__, _true__, _false_]"), SV("{::_^{}}"), input, 7); + check(SV("[:::true, :::true, ::false]"), SV("{:::>{}}"), input, 7); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier for a bool does not allow the sign option", SV("{::-}"), input); + check_exception("The format specifier for a bool does not allow the sign option", SV("{::+}"), input); + check_exception("The format specifier for a bool does not allow the sign option", SV("{:: }"), input); + + check(SV("[1, 1, 0]"), SV("{::-d}"), input); + check(SV("[+1, +1, +0]"), SV("{::+d}"), input); + check(SV("[ 1, 1, 0]"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception("The format specifier for a bool does not allow the alternate form option", SV("{::#}"), input); + + check(SV("[0x1, 0x1, 0x0]"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("The format specifier for a bool does not allow the zero-padding option", SV("{::05}"), input); + + check(SV("[00001, 00001, 00000]"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[true, true, false]"), SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBdosxX")) { + check_exception("The type option contains an invalid value for a bool formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^32::>7}"), input); + check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^{}::>7}"), input, 32); + check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^{}::>{}}"), input, 32, 7); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 32); +} + +template +void test_bool(TestFunction check, ExceptionTest check_exception) { + array input{true, true, false}; + test_bool(check, check_exception, queue{input.begin(), input.end()}); + test_bool(check, check_exception, priority_queue{input.begin(), input.end()}); + test_bool(check, check_exception, stack{input.begin(), input.end()}); +} + +// +// Integral +// + +template +void test_int(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[-42, 1, 2, 42]"), SV("{}"), input); + check(SV("[-42, 1, 2, 42]^42"), SV("{}^42"), input); + check(SV("[-42, 1, 2, 42]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[-42, 1, 2, 42] "), SV("{:20}"), input); + check(SV("[-42, 1, 2, 42]*****"), SV("{:*<20}"), input); + check(SV("__[-42, 1, 2, 42]___"), SV("{:_^20}"), input); + check(SV("#####[-42, 1, 2, 42]"), SV("{:#>20}"), input); + + check(SV("[-42, 1, 2, 42] "), SV("{:{}}"), input, 20); + check(SV("[-42, 1, 2, 42]*****"), SV("{:*<{}}"), input, 20); + check(SV("__[-42, 1, 2, 42]___"), SV("{:_^{}}"), input, 20); + check(SV("#####[-42, 1, 2, 42]"), SV("{:#>{}}"), input, 20); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__-42, 1, 2, 42___"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[ -42, 1, 2, 42]"), SV("{::5}"), input); + check(SV("[-42**, 1****, 2****, 42***]"), SV("{::*<5}"), input); + check(SV("[_-42_, __1__, __2__, _42__]"), SV("{::_^5}"), input); + check(SV("[::-42, ::::1, ::::2, :::42]"), SV("{:::>5}"), input); + + check(SV("[ -42, 1, 2, 42]"), SV("{::{}}"), input, 5); + check(SV("[-42**, 1****, 2****, 42***]"), SV("{::*<{}}"), input, 5); + check(SV("[_-42_, __1__, __2__, _42__]"), SV("{::_^{}}"), input, 5); + check(SV("[::-42, ::::1, ::::2, :::42]"), SV("{:::>{}}"), input, 5); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check(SV("[-42, 1, 2, 42]"), SV("{::-}"), input); + check(SV("[-42, +1, +2, +42]"), SV("{::+}"), input); + check(SV("[-42, 1, 2, 42]"), SV("{:: }"), input); + + // *** alternate form *** + check(SV("[-0x2a, 0x1, 0x2, 0x2a]"), SV("{::#x}"), input); + + // *** zero-padding *** + check(SV("[-0042, 00001, 00002, 00042]"), SV("{::05}"), input); + check(SV("[-002a, 00001, 00002, 0002a]"), SV("{::05x}"), input); + check(SV("[-0x2a, 0x001, 0x002, 0x02a]"), SV("{::#05x}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[-42, 1, 2, 42]"), SV("{::L}"), input); // does nothing in this test, but is accepted. + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("bBcdoxX")) { + check_exception("The type option contains an invalid value for an integer formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[::-42, ::::1, ::::2, :::42]^^^"), SV("{:^^33::>5}"), input); + check(SV("^^[::-42, ::::1, ::::2, :::42]^^^"), SV("{:^^{}::>5}"), input, 33); + check(SV("^^[::-42, ::::1, ::::2, :::42]^^^"), SV("{:^^{}::>{}}"), input, 33, 5); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 33); +} + +template +void test_int(TestFunction check, ExceptionTest check_exception) { + array input{-42, 1, 2, 42}; + test_int(check, check_exception, queue{input.begin(), input.end()}); + test_int(check, check_exception, priority_queue{input.begin(), input.end(), greater{}}); + test_int(check, check_exception, stack{input.begin(), input.end()}); +} + +// +// Floating point +// + +template +void test_floating_point(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{}"), input); + check(SV("[-42.5, 0, 1.25, 42.5]^42"), SV("{}^42"), input); + check(SV("[-42.5, 0, 1.25, 42.5]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[-42.5, 0, 1.25, 42.5] "), SV("{:27}"), input); + check(SV("[-42.5, 0, 1.25, 42.5]*****"), SV("{:*<27}"), input); + check(SV("__[-42.5, 0, 1.25, 42.5]___"), SV("{:_^27}"), input); + check(SV("#####[-42.5, 0, 1.25, 42.5]"), SV("{:#>27}"), input); + + check(SV("[-42.5, 0, 1.25, 42.5] "), SV("{:{}}"), input, 27); + check(SV("[-42.5, 0, 1.25, 42.5]*****"), SV("{:*<{}}"), input, 27); + check(SV("__[-42.5, 0, 1.25, 42.5]___"), SV("{:_^{}}"), input, 27); + check(SV("#####[-42.5, 0, 1.25, 42.5]"), SV("{:#>{}}"), input, 27); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__-42.5, 0, 1.25, 42.5___"), SV("{:_^25n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::5}"), input); + check(SV("[-42.5, 0****, 1.25*, 42.5*]"), SV("{::*<5}"), input); + check(SV("[-42.5, __0__, 1.25_, 42.5_]"), SV("{::_^5}"), input); + check(SV("[-42.5, ::::0, :1.25, :42.5]"), SV("{:::>5}"), input); + + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::{}}"), input, 5); + check(SV("[-42.5, 0****, 1.25*, 42.5*]"), SV("{::*<{}}"), input, 5); + check(SV("[-42.5, __0__, 1.25_, 42.5_]"), SV("{::_^{}}"), input, 5); + check(SV("[-42.5, ::::0, :1.25, :42.5]"), SV("{:::>{}}"), input, 5); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::-}"), input); + check(SV("[-42.5, +0, +1.25, +42.5]"), SV("{::+}"), input); + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{:: }"), input); + + // *** alternate form *** + check(SV("[-42.5, 0., 1.25, 42.5]"), SV("{::#}"), input); + + // *** zero-padding *** + check(SV("[-42.5, 00000, 01.25, 042.5]"), SV("{::05}"), input); + check(SV("[-42.5, 0000., 01.25, 042.5]"), SV("{::#05}"), input); + + // *** precision *** + check(SV("[-42, 0, 1.2, 42]"), SV("{::.2}"), input); + check(SV("[-42.500, 0.000, 1.250, 42.500]"), SV("{::.3f}"), input); + + check(SV("[-42, 0, 1.2, 42]"), SV("{::.{}}"), input, 2); + check(SV("[-42.500, 0.000, 1.250, 42.500]"), SV("{::.{}f}"), input, 3); + + check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::L}"), input); // does not require locales present + +#if !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + locale::global(locale("fr-FR")); + check(SV("[-42,5, 0, 1,25, 42,5]"), SV("{::L}"), input); + + locale::global(locale("en-US")); + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::L}"), input); + + locale::global(locale::classic()); +#endif // !defined(_DLL) || _ITERATOR_DEBUG_LEVEL == DEFAULT_IDL_SETTING + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("aAeEfFgG")) { + check_exception( + "The type option contains an invalid value for a floating-point formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^33::>5}"), input); + check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^{}::>5}"), input, 33); + check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^{}::>{}}"), input, 33, 5); + + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^33::>5.2}"), input); + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>5.2}"), input, 33); + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>{}.2}"), input, 33, 5); + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>{}.{}}"), input, 33, 5, 2); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5.2}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}.2}"), input, 33); + check_exception("The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}.{}}"), + input, 33, 5); +} + +template +void test_floating_point(TestFunction check, ExceptionTest check_exception) { + array input{-42.5l, 0.0l, 1.25l, 42.5l}; + test_floating_point(check, check_exception, queue{input.begin(), input.end()}); + test_floating_point(check, check_exception, priority_queue{input.begin(), input.end(), greater{}}); + test_floating_point(check, check_exception, stack{input.begin(), input.end()}); +} + +// +// Pointer +// + +template +void test_pointer(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[0x0]"), SV("{}"), input); + check(SV("[0x0]^42"), SV("{}^42"), input); + check(SV("[0x0]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[0x0] "), SV("{:10}"), input); + check(SV("[0x0]*****"), SV("{:*<10}"), input); + check(SV("__[0x0]___"), SV("{:_^10}"), input); + check(SV("#####[0x0]"), SV("{:#>10}"), input); + + check(SV("[0x0] "), SV("{:{}}"), input, 10); + check(SV("[0x0]*****"), SV("{:*<{}}"), input, 10); + check(SV("__[0x0]___"), SV("{:_^{}}"), input, 10); + check(SV("#####[0x0]"), SV("{:#>{}}"), input, 10); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("_0x0_"), SV("{:_^5n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV("[ 0x0]"), SV("{::5}"), input); + check(SV("[0x0**]"), SV("{::*<5}"), input); + check(SV("[_0x0_]"), SV("{::_^5}"), input); + check(SV("[::0x0]"), SV("{:::>5}"), input); + + check(SV("[ 0x0]"), SV("{::{}}"), input, 5); + check(SV("[0x0**]"), SV("{::*<{}}"), input, 5); + check(SV("[_0x0_]"), SV("{::_^{}}"), input, 5); + check(SV("[::0x0]"), SV("{:::>{}}"), input, 5); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check(SV("[0x0000]"), SV("{::06}"), input); + check(SV("[0x0000]"), SV("{::06p}"), input); + check(SV("[0X0000]"), SV("{::06P}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("pP")) { + check_exception("The type option contains an invalid value for a pointer formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV("^^[::0x0]^^^"), SV("{:^^12::>5}"), input); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>5}"), input, 12); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>{}}"), input, 12, 5); + + check(SV("^^[::0x0]^^^"), SV("{:^^12::>5}"), input); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>5}"), input, 12); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>{}}"), input, 12, 5); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>5}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 12); +} + +template +void test_pointer(TestFunction check, ExceptionTest check_exception) { + array input{static_cast(0)}; + test_pointer(check, check_exception, queue{input.begin(), input.end()}); + test_pointer(check, check_exception, priority_queue{input.begin(), input.end()}); + test_pointer(check, check_exception, stack{input.begin(), input.end()}); +} + +// +// String +// + +template +void test_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV(R"(["Hello", "world"])"), SV("{}"), input); + check(SV(R"(["Hello", "world"]^42)"), SV("{}^42"), input); + check(SV(R"(["Hello", "world"]^42)"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"(["Hello", "world"] )"), SV("{:23}"), input); + check(SV(R"(["Hello", "world"]*****)"), SV("{:*<23}"), input); + check(SV(R"(__["Hello", "world"]___)"), SV("{:_^23}"), input); + check(SV(R"(#####["Hello", "world"])"), SV("{:#>23}"), input); + + check(SV(R"(["Hello", "world"] )"), SV("{:{}}"), input, 23); + check(SV(R"(["Hello", "world"]*****)"), SV("{:*<{}}"), input, 23); + check(SV(R"(__["Hello", "world"]___)"), SV("{:_^{}}"), input, 23); + check(SV(R"(#####["Hello", "world"])"), SV("{:#>{}}"), input, 23); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV(R"(_"Hello", "world"_)"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check(SV(R"([Hello , world ])"), SV("{::8}"), input); + check(SV(R"([Hello***, world***])"), SV("{::*<8}"), input); + check(SV(R"([_Hello__, _world__])"), SV("{::_^8}"), input); + check(SV(R"([:::Hello, :::world])"), SV("{:::>8}"), input); + + check(SV(R"([Hello , world ])"), SV("{::{}}"), input, 8); + check(SV(R"([Hello***, world***])"), SV("{::*<{}}"), input, 8); + check(SV(R"([_Hello__, _world__])"), SV("{::_^{}}"), input, 8); + check(SV(R"([:::Hello, :::world])"), SV("{:::>{}}"), input, 8); + + check_exception("The format string contains an invalid escape sequence", SV("{::}<}"), input); + check_exception("The fill option contains an invalid value", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{::05}"), input); + + // *** precision *** + check(SV(R"([Hel, wor])"), SV("{::.3}"), input); + + check(SV(R"([Hel, wor])"), SV("{::.{}}"), input, 3); + + check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (basic_string_view fmt : fmt_invalid_nested_types("s?")) { + check_exception("The type option contains an invalid value for a string formatting argument", fmt, input); + } + + // ***** Both have a format-spec + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>8}"), input); + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 25); +} + +template +void test_string(TestFunction check, ExceptionTest check_exception) { + array input{STR("Hello"), STR("world")}; + test_string(check, check_exception, queue{input.begin(), input.end()}); + test_string(check, check_exception, priority_queue{input.begin(), input.end(), greater{}}); + test_string(check, check_exception, stack{input.begin(), input.end()}); +} + +// +// Handle +// + +template +void test_status(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[0xaaaa, 0x5555, 0xaa55]"), SV("{}"), input); + check(SV("[0xaaaa, 0x5555, 0xaa55]^42"), SV("{}^42"), input); + check(SV("[0xaaaa, 0x5555, 0xaa55]^42"), SV("{:}^42"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[0xaaaa, 0x5555, 0xaa55] "), SV("{:29}"), input); + check(SV("[0xaaaa, 0x5555, 0xaa55]*****"), SV("{:*<29}"), input); + check(SV("__[0xaaaa, 0x5555, 0xaa55]___"), SV("{:_^29}"), input); + check(SV("#####[0xaaaa, 0x5555, 0xaa55]"), SV("{:#>29}"), input); + + check(SV("[0xaaaa, 0x5555, 0xaa55] "), SV("{:{}}"), input, 29); + check(SV("[0xaaaa, 0x5555, 0xaa55]*****"), SV("{:*<{}}"), input, 29); + check(SV("__[0xaaaa, 0x5555, 0xaa55]___"), SV("{:_^{}}"), input, 29); + check(SV("#####[0xaaaa, 0x5555, 0xaa55]"), SV("{:#>{}}"), input, 29); + + check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input); + check_exception("The fill option contains an invalid value", SV("{:{<}"), input); + + // *** sign *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format specifier should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("The width option should not have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__0xaaaa, 0x5555, 0xaa55___"), SV("{:_^27n}"), input); + + // *** type *** + check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input); + check_exception("Type s requires character type as formatting argument", SV("{:s}"), input); + check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input); + for (basic_string_view fmt : fmt_invalid_types("s")) { + check_exception("The format specifier should consume the input or end with a '}'", fmt, input); + } + + // ***** Only underlying has a format-spec + check_exception("The type option contains an invalid value for a status formatting argument", SV("{::*<7}"), input); + for (basic_string_view fmt : fmt_invalid_nested_types("sxX")) { + check_exception("The type option contains an invalid value for a status formatting argument", fmt, input); + } + + check(SV("[0xaaaa, 0x5555, 0xaa55]"), SV("{::x}"), input); + check(SV("[0XAAAA, 0X5555, 0XAA55]"), SV("{::X}"), input); + check(SV("[foo, bar, foobar]"), SV("{::s}"), input); + + // ***** Both have a format-spec + check(SV("^^[0XAAAA, 0X5555, 0XAA55]^^^"), SV("{:^^29:X}"), input); + check(SV("^^[0XAAAA, 0X5555, 0XAA55]^^^"), SV("{:^^{}:X}"), input, 29); + + check_exception( + "The argument index value is too large for the number of arguments supplied", SV("{:^^{}:X}"), input); +} + +template +void test_status(TestFunction check, ExceptionTest check_exception) { + array input{status::foo, status::bar, status::foobar}; + test_status(check, check_exception, queue{input.begin(), input.end()}); + test_status(check, check_exception, priority_queue{input.begin(), input.end(), less{}}); + test_status(check, check_exception, stack{input.begin(), input.end()}); +} + +// +// Driver +// + +template +void format_tests(TestFunction check, ExceptionTest check_exception) { + test_char(check, check_exception); + if (same_as) { // avoid testing twice + test_char_to_wchar(check, check_exception); + } + test_bool(check, check_exception); + test_int(check, check_exception); + test_floating_point(check, check_exception); + test_pointer(check, check_exception); + test_string(check, check_exception); + + test_status(check, check_exception); // Has its own handler with its own parser +} + +auto test_format = [](basic_string_view expected, + type_identity_t> fmt, Args&&... args) { + basic_string out = format(fmt, forward(args)...); + assert(out == expected); +}; + +auto test_format_exception = [](string_view, basic_string_view, Args&&...) { + // After P2216 most exceptions thrown by format become ill-formed. + // Therefore this test does nothing. +}; + +auto test_vformat = []( + basic_string_view expected, basic_string_view fmt, Args&&... args) { + basic_string out = vformat(fmt, make_format_args>(args...)); + assert(out == expected); +}; + +auto test_vformat_exception = []([[maybe_unused]] string_view what, + [[maybe_unused]] basic_string_view fmt, [[maybe_unused]] Args&&... args) { + try { + static_cast(vformat(fmt, make_format_args>(args...))); + assert(false); + } catch (const format_error&) { + } catch (...) { + assert(false); + } +}; + +int main() { + format_tests(test_format, test_format_exception); + format_tests(test_format, test_format_exception); + + format_tests(test_vformat, test_vformat_exception); + format_tests(test_vformat, test_vformat_exception); +} diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index 53a238a003..8985e3032c 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -359,6 +359,12 @@ STATIC_ASSERT(__cpp_lib_format == 202304L); #error __cpp_lib_format is defined #endif +#if _HAS_CXX23 +STATIC_ASSERT(__cpp_lib_format_ranges == 202207L); +#elif defined(__cpp_lib_format_ranges) +#error __cpp_lib_format_ranges is defined +#endif + #if _HAS_CXX20 STATIC_ASSERT(__cpp_lib_format_uchar == 202311L); #elif defined(__cpp_lib_format_uchar) From 52498823360f772e43419df5677de81d5381d51a Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Thu, 11 Jul 2024 13:42:01 +0800 Subject: [PATCH 2/7] Fix weird comments --- stl/inc/yvals_core.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 2a72105b68..86298b4e5d 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -356,11 +356,9 @@ // P2165R4 Compatibility Between tuple, pair, And tuple-like Objects // P2166R1 Prohibiting basic_string And basic_string_view Construction From nullptr // P2186R2 Removing Garbage Collection Support -// P2286R8 Formatting Ranges // P2273R3 constexpr unique_ptr // P2278R4 cbegin Should Always Return A Constant Iterator // P2286R8 Formatting Ranges -// (only the '?' format specifier for strings and characters) // P2291R3 constexpr Integral // P2302R4 ranges::contains, ranges::contains_subrange // P2321R2 zip From 3280357098fd2b08e612577c35ec735e1d959bdc Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Aug 2024 00:51:38 -0700 Subject: [PATCH 3/7] Centralize `_Adaptor_formatter_base`. Using `_AdaptorType::container_type` avoids having to redundantly pass it. `template class _RefView` works around the fact that `_RANGES ref_view` isn't available in ``. --- stl/inc/format | 24 ++++++++++++++++++++++++ stl/inc/queue | 44 ++++---------------------------------------- stl/inc/stack | 21 ++------------------- 3 files changed, 30 insertions(+), 59 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index 4f095cc2ea..c9d1cac0ea 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -4786,6 +4786,30 @@ consteval auto _Get_member_pointer_to_adapted_container() noexcept { }; return &_Container_exposer::c; } + +// Per LWG-3997, `_CharT` in library-provided `formatter` specializations is +// constrained to character types supported by `format`. +template class _RefView> +struct _Adaptor_formatter_base { +private: + using _Container = _AdaptorType::container_type; + using _Maybe_const_container = _Fmt_maybe_const<_Container, _CharT>; + using _Maybe_const_adaptor = _Maybe_const, _AdaptorType>; + + formatter<_RefView<_Maybe_const_container>, _CharT> _Underlying; + +public: + template + constexpr _ParseCtx::iterator parse(_ParseCtx& _Ctx) { + return _Underlying.parse(_Ctx); + } + + template + _FmtCtx::iterator format(_Maybe_const_adaptor& _Adaptor, _FmtCtx& _Ctx) const { + constexpr auto _Mem_cont_ptr = _STD _Get_member_pointer_to_adapted_container<_AdaptorType>(); + return _Underlying.format(_Adaptor.*_Mem_cont_ptr, _Ctx); + } +}; #endif // _HAS_CXX23 _STD_END diff --git a/stl/inc/queue b/stl/inc/queue index 271e2083b8..832ed8fbc3 100644 --- a/stl/inc/queue +++ b/stl/inc/queue @@ -228,25 +228,8 @@ struct uses_allocator, _Alloc> : uses_allocator<_Containe // Per LWG-3997, `_CharT` in library-provided `formatter` specializations is // constrained to character types supported by `format`. template <_Format_supported_charT _CharT, class _Ty, formattable<_CharT> _Container> -struct formatter, _CharT> { -private: - using _Maybe_const_container = _Fmt_maybe_const<_Container, _CharT>; - using _Maybe_const_adaptor = _Maybe_const, queue<_Ty, _Container>>; - - formatter<_RANGES ref_view<_Maybe_const_container>, _CharT> _Underlying; - -public: - template - constexpr _ParseCtx::iterator parse(_ParseCtx& _Ctx) { - return _Underlying.parse(_Ctx); - } - - template - _FmtCtx::iterator format(_Maybe_const_adaptor& _Adaptor, _FmtCtx& _Ctx) const { - constexpr auto _Mem_cont_ptr = _STD _Get_member_pointer_to_adapted_container>(); - return _Underlying.format(_Adaptor.*_Mem_cont_ptr, _Ctx); - } -}; +struct formatter, _CharT> + : _Adaptor_formatter_base, _CharT, _RANGES ref_view> {}; #endif // _HAS_CXX23 _EXPORT_STD template , class _Pr = less> @@ -520,27 +503,8 @@ struct uses_allocator, _Alloc> : uses_alloc // Per LWG-3997, `_CharT` in library-provided `formatter` specializations is // constrained to character types supported by `format`. template <_Format_supported_charT _CharT, class _Ty, formattable<_CharT> _Container, class _Comp> -struct formatter, _CharT> { -private: - using _Maybe_const_container = _Fmt_maybe_const<_Container, _CharT>; - using _Maybe_const_adaptor = - _Maybe_const, priority_queue<_Ty, _Container, _Comp>>; - - formatter<_RANGES ref_view<_Maybe_const_container>, _CharT> _Underlying; - -public: - template - constexpr _ParseCtx::iterator parse(_ParseCtx& _Ctx) { - return _Underlying.parse(_Ctx); - } - - template - _FmtCtx::iterator format(_Maybe_const_adaptor& _Adaptor, _FmtCtx& _Ctx) const { - constexpr auto _Mem_cont_ptr = - _STD _Get_member_pointer_to_adapted_container>(); - return _Underlying.format(_Adaptor.*_Mem_cont_ptr, _Ctx); - } -}; +struct formatter, _CharT> + : _Adaptor_formatter_base, _CharT, _RANGES ref_view> {}; #endif // _HAS_CXX23 _STD_END diff --git a/stl/inc/stack b/stl/inc/stack index ba3635c1d4..0993120233 100644 --- a/stl/inc/stack +++ b/stl/inc/stack @@ -215,25 +215,8 @@ struct uses_allocator, _Alloc> : uses_allocator<_Containe // Per LWG-3997, `_CharT` in library-provided `formatter` specializations is // constrained to character types supported by `format`. template <_Format_supported_charT _CharT, class _Ty, formattable<_CharT> _Container> -struct formatter, _CharT> { -private: - using _Maybe_const_container = _Fmt_maybe_const<_Container, _CharT>; - using _Maybe_const_adaptor = _Maybe_const, stack<_Ty, _Container>>; - - formatter<_RANGES ref_view<_Maybe_const_container>, _CharT> _Underlying; - -public: - template - constexpr _ParseCtx::iterator parse(_ParseCtx& _Ctx) { - return _Underlying.parse(_Ctx); - } - - template - _FmtCtx::iterator format(_Maybe_const_adaptor& _Adaptor, _FmtCtx& _Ctx) const { - constexpr auto _Mem_cont_ptr = _STD _Get_member_pointer_to_adapted_container>(); - return _Underlying.format(_Adaptor.*_Mem_cont_ptr, _Ctx); - } -}; +struct formatter, _CharT> + : _Adaptor_formatter_base, _CharT, _RANGES ref_view> {}; #endif // _HAS_CXX23 _STD_END From bcdd31164d90f200ff42790994fe57376eecee1f Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Aug 2024 01:05:40 -0700 Subject: [PATCH 4/7] Fuse `_Get_member_pointer_to_adapted_container` into `_Adaptor_formatter_base::format`. --- stl/inc/format | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/stl/inc/format b/stl/inc/format index c9d1cac0ea..c2af86f05a 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -4775,18 +4775,6 @@ private: _Fill_align_and_width_specs<_CharT> _Specs; }; -template -consteval auto _Get_member_pointer_to_adapted_container() noexcept { - struct _Container_exposer : _Adaptor { - using _Adaptor::c; - - _Container_exposer(const _Container_exposer&) = delete; - _Container_exposer& operator=(const _Container_exposer&) = delete; - ~_Container_exposer() = delete; - }; - return &_Container_exposer::c; -} - // Per LWG-3997, `_CharT` in library-provided `formatter` specializations is // constrained to character types supported by `format`. template class _RefView> @@ -4806,7 +4794,15 @@ public: template _FmtCtx::iterator format(_Maybe_const_adaptor& _Adaptor, _FmtCtx& _Ctx) const { - constexpr auto _Mem_cont_ptr = _STD _Get_member_pointer_to_adapted_container<_AdaptorType>(); + struct _Container_exposer : _AdaptorType { + using _AdaptorType::c; + + _Container_exposer(const _Container_exposer&) = delete; + _Container_exposer& operator=(const _Container_exposer&) = delete; + ~_Container_exposer() = delete; + }; + + constexpr auto _Mem_cont_ptr = &_Container_exposer::c; return _Underlying.format(_Adaptor.*_Mem_cont_ptr, _Ctx); } }; From 59b912c16a46fbe55cf5755016adf9fb46aa01b8 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Aug 2024 01:12:52 -0700 Subject: [PATCH 5/7] Hyphenate LWG citations. --- .../tests/P2286R8_text_formatting_container_adaptors/test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp b/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp index 9070ec6617..ce11b83876 100644 --- a/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp @@ -111,7 +111,7 @@ enum class status : uint16_t { foo = 0xAAAA, bar = 0x5555, foobar = 0xAA55 }; template struct formatter { // During the 2023 Issaquah meeting LEWG made it clear a formatter is - // required to call its parse function. LWG3892 Adds the wording for that + // required to call its parse function. LWG-3892 Adds the wording for that // requirement. Therefore this formatter is initialized in an invalid state. // A call to parse sets it in a valid state and a call to format validates // the state. @@ -446,7 +446,7 @@ void test_char(TestFunction check, ExceptionTest check_exception) { test_char_escaped_string(check, check_exception, priority_queue{input.begin(), input.end(), greater{}}); test_char_escaped_string(check, check_exception, stack{input.begin(), input.end()}); - // LWG3881 fixes formatting container adaptors backed by a string. + // LWG-3881 fixes formatting container adaptors backed by a string. test_char_default(check, check_exception, queue{basic_string{input.begin(), input.end()}}); test_char_default( check, check_exception, priority_queue{greater{}, basic_string{input.begin(), input.end()}}); From 80c4854784ad28b3e6666f00587c13cb942e219a Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Aug 2024 02:24:03 -0700 Subject: [PATCH 6/7] 0 => nullptr --- .../tests/P2286R8_text_formatting_container_adaptors/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp b/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp index ce11b83876..1c8fece08e 100644 --- a/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp @@ -951,7 +951,7 @@ void test_pointer(TestFunction check, ExceptionTest check_exception, auto&& inpu template void test_pointer(TestFunction check, ExceptionTest check_exception) { - array input{static_cast(0)}; + array input{static_cast(nullptr)}; test_pointer(check, check_exception, queue{input.begin(), input.end()}); test_pointer(check, check_exception, priority_queue{input.begin(), input.end()}); test_pointer(check, check_exception, stack{input.begin(), input.end()}); From 076e5a96fc0df9ede0454f3c7d4270d45c18e54e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 12 Aug 2024 02:26:52 -0700 Subject: [PATCH 7/7] Include more headers. But not you, ``! You're not cool enough. --- .../test.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp b/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp index 1c8fece08e..84cad46b15 100644 --- a/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp +++ b/tests/std/tests/P2286R8_text_formatting_container_adaptors/test.cpp @@ -18,13 +18,24 @@ #include #include #include +#include #include #include +#include +#include +#include #include -#include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include #include #include