Implement `formatter` specializations for `pair` and `tuple` (#4438)

Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
This commit is contained in:
A. Jiang 2024-03-16 13:09:48 +08:00 коммит произвёл GitHub
Родитель 4d088fc77c
Коммит 79e79a2e07
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
9 изменённых файлов: 734 добавлений и 23 удалений

Просмотреть файл

@ -15,7 +15,7 @@
"type": "git",
"git": {
"repositoryUrl": "https://github.com/llvm/llvm-project.git",
"commitHash": "b8d38e8b4fcab071c5c4cb698e154023d06de69e"
"commitHash": "2e2b6b53f5f63179b52168ee156df7c76b90bc71"
}
}
},

Просмотреть файл

@ -127,9 +127,9 @@ concept _Format_supported_charT = _Is_any_of_v<_CharT, char, wchar_t>;
// makes it "disabled" as per N4950 [format.formatter.spec]/5
_EXPORT_STD template <class _Ty, class _CharT = char>
struct formatter {
formatter() = delete;
formatter(const formatter&) = delete;
formatter operator=(const formatter&) = delete;
formatter() = delete;
formatter(const formatter&) = delete;
formatter& operator=(const formatter&) = delete;
};
_FMT_P2286_BEGIN
@ -266,6 +266,25 @@ struct formatter<basic_string_view<_CharT, _Traits>, _CharT>
}
#endif // _HAS_CXX23
};
#if _HAS_CXX23
_EXPORT_STD template <class, class>
struct pair;
_EXPORT_STD template <class...>
class tuple;
// Specializations for pairs and tuples are forward-declared to avoid any risk of using the disabled primary template.
// Per LWG-3997, `_CharT` in library-provided `formatter` specializations is
// constrained to character types supported by `format`.
template <_Format_supported_charT _CharT, class _Ty1, class _Ty2>
struct formatter<pair<_Ty1, _Ty2>, _CharT>;
template <_Format_supported_charT _CharT, class... _Types>
struct formatter<tuple<_Types...>, _CharT>;
#endif // _HAS_CXX23
_STD_END
#pragma pop_macro("new")

Просмотреть файл

@ -58,6 +58,10 @@ _EMIT_STL_WARNING(STL4038, "The contents of <format> are available only with C++
#include <xstring>
#include <xutility>
#if _HAS_CXX23
#include <tuple>
#endif // _HAS_CXX23
#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
@ -3934,6 +3938,259 @@ _NODISCARD size_t formatted_size(const locale& _Loc, const wformat_string<_Types
_FMT_P2286_END
#if _HAS_CXX23
enum class _Fmt_tuple_type : uint8_t { _None, _Key_value, _No_brackets };
template <class _CharT>
struct _Fill_align_and_width_specs { // used by pair, tuple, thread::id, and stacktrace_entry formatters
int _Width = -1;
int _Dynamic_width_index = -1;
_Fmt_align _Alignment = _Fmt_align::_None;
uint8_t _Fill_length = 1;
// At most one codepoint (so one char32_t or four utf-8 char8_t).
_CharT _Fill[4 / sizeof(_CharT)]{' '};
};
template <class _CharT, bool _IsTwoTuple>
class _Tuple_format_specs_setter {
public:
constexpr explicit _Tuple_format_specs_setter(_Fill_align_and_width_specs<_CharT>& _Specs_,
_Fmt_tuple_type& _Fmt_type_, basic_format_parse_context<_CharT>& _Parse_ctx_)
: _Specs(_Specs_), _Fmt_type(_Fmt_type_), _Parse_ctx(_Parse_ctx_) {}
constexpr void _On_align(const _Fmt_align _Aln) {
_Specs._Alignment = _Aln;
}
constexpr void _On_fill(const basic_string_view<_CharT> _Sv) {
if (_Sv.size() > _STD size(_Specs._Fill)) {
_Throw_format_error("Invalid fill (too long).");
}
if (_Sv == _STATICALLY_WIDEN(_CharT, ":")) {
_Throw_format_error(R"(Invalid fill ":" for tuples and pairs.)");
}
const auto _Pos = _STD _Copy_unchecked(_Sv._Unchecked_begin(), _Sv._Unchecked_end(), _Specs._Fill);
_STD fill(_Pos, _STD end(_Specs._Fill), _CharT{});
_Specs._Fill_length = static_cast<uint8_t>(_Sv.size());
}
constexpr void _On_width(const int _Width) {
_Specs._Width = _Width;
}
constexpr void _On_dynamic_width(const size_t _Arg_id) {
_Parse_ctx.check_arg_id(_Arg_id);
_Parse_ctx._Check_dynamic_spec_integral(_Arg_id);
_Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id);
}
constexpr void _On_dynamic_width(_Auto_id_tag) {
const size_t _Arg_id = _Parse_ctx.next_arg_id();
_Parse_ctx._Check_dynamic_spec_integral(_Arg_id);
_Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id);
}
constexpr void _On_type(const _CharT _Type) {
if (_Type == _CharT{}) {
return;
}
if (_Type == static_cast<_CharT>('n')) {
_Fmt_type = _Fmt_tuple_type::_No_brackets;
return;
}
if constexpr (_IsTwoTuple) {
if (_Type == static_cast<_CharT>('m')) {
_Fmt_type = _Fmt_tuple_type::_Key_value;
return;
}
}
_Throw_format_error("Invalid type specification.");
}
private:
_Fill_align_and_width_specs<_CharT>& _Specs;
_Fmt_tuple_type& _Fmt_type;
basic_format_parse_context<_CharT>& _Parse_ctx;
_NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) {
if (!_STD in_range<int>(_Idx)) {
_Throw_format_error("Dynamic width index is too large.");
}
return static_cast<int>(_Idx);
}
};
template <class _CharT, class _Callbacks_type>
_NODISCARD constexpr const _CharT* _Parse_tuple_format_specs(
const _CharT* _Begin, const _CharT* _End, _Callbacks_type&& _Callbacks) {
if (_Begin == _End || *_Begin == '}') {
return _Begin;
}
_Begin = _STD _Parse_align(_Begin, _End, _Callbacks);
if (_Begin == _End) {
return _Begin;
}
_Begin = _STD _Parse_width(_Begin, _End, _Callbacks);
if (_Begin == _End) {
return _Begin;
}
// If there's anything remaining we assume it's a type.
if (*_Begin != '}') {
_Callbacks._On_type(*_Begin);
++_Begin;
} else {
// call the type callback so it gets a default type, this is required
// since _Specs_checker needs to be able to tell that it got a default type
// to raise an error for default formatted bools with a sign modifier
_Callbacks._On_type(_CharT{});
}
return _Begin;
}
template <class _FormatterType, class _ParseContext>
constexpr void _Set_tuple_debug_format(_FormatterType& _Formatter, _ParseContext& _Parse_ctx) {
_Formatter.parse(_Parse_ctx);
if constexpr (requires { _Formatter.set_debug_format(); }) {
_Formatter.set_debug_format();
}
}
template <class _CharT, formattable<_CharT>... _Types>
class _Tuple_formatter_common_base {
private:
tuple<formatter<remove_cvref_t<_Types>, _CharT>...> _Underlying;
basic_string_view<_CharT> _Separator = _STATICALLY_WIDEN(_CharT, ", ");
basic_string_view<_CharT> _Opening_bracket = _STATICALLY_WIDEN(_CharT, "(");
basic_string_view<_CharT> _Closing_bracket = _STATICALLY_WIDEN(_CharT, ")");
_Fill_align_and_width_specs<_CharT> _Specs;
protected:
static constexpr bool _Is_const_formattable = (formattable<const _Types, _CharT> && ...);
template <class _FormatContext, class... _ArgTypes>
_FormatContext::iterator _Format(_FormatContext& _Fmt_ctx, _ArgTypes&... _Args) const {
_STL_INTERNAL_STATIC_ASSERT(
(is_same_v<_ArgTypes, remove_reference_t<_Maybe_const<_Is_const_formattable, _Types>>> && ...));
auto _Format_specs = _Specs;
if (_Specs._Dynamic_width_index >= 0) {
_Format_specs._Width =
_STD _Get_dynamic_specs<_Width_checker>(_Fmt_ctx.arg(static_cast<size_t>(_Specs._Dynamic_width_index)));
}
basic_string<_CharT> _Tmp_buf;
basic_format_context<back_insert_iterator<basic_string<_CharT>>, _CharT> _Tmp_ctx{
_STD back_inserter(_Tmp_buf), {}, _Fmt_ctx._Get_lazy_locale()};
_STD _Copy_unchecked(_Opening_bracket._Unchecked_begin(), _Opening_bracket._Unchecked_end(), _Tmp_ctx.out());
[&]<size_t... _Indices>(index_sequence<_Indices...>) {
auto _Single_writer = [&]<size_t _Idx>(auto& _Arg) {
if constexpr (_Idx != 0) {
_STD _Copy_unchecked(_Separator._Unchecked_begin(), _Separator._Unchecked_end(), _Tmp_ctx.out());
}
_STD get<_Idx>(_Underlying).format(_Arg, _Tmp_ctx);
};
(_Single_writer.template operator()<_Indices>(_Args), ...);
}(index_sequence_for<_ArgTypes...>{});
_STD _Copy_unchecked(_Closing_bracket._Unchecked_begin(), _Closing_bracket._Unchecked_end(), _Tmp_ctx.out());
return _STD _Write_aligned(_Fmt_ctx.out(), static_cast<int>(_Tmp_buf.size()), _Format_specs, _Fmt_align::_Left,
[&](typename _FormatContext::iterator _Out) {
return _STD _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Tmp_buf});
});
}
public:
constexpr void set_separator(const basic_string_view<_CharT> _Sep) noexcept {
_Separator = _Sep;
}
constexpr void set_brackets(
const basic_string_view<_CharT> _Opening, const basic_string_view<_CharT> _Closing) noexcept {
_Opening_bracket = _Opening;
_Closing_bracket = _Closing;
}
template <class _ParseContext>
constexpr _ParseContext::iterator parse(_ParseContext& _Ctx) {
_Fmt_tuple_type _Fmt_type = _Fmt_tuple_type::_None;
_Tuple_format_specs_setter<_CharT, sizeof...(_Types) == 2> _Callback{_Specs, _Fmt_type, _Ctx};
const auto _It = _STD _Parse_tuple_format_specs(_Ctx._Unchecked_begin(), _Ctx._Unchecked_end(), _Callback);
if (_It != _Ctx._Unchecked_end() && *_It != '}') {
_STD _Throw_format_error("Missing '}' in format string.");
}
if (_Fmt_type == _Fmt_tuple_type::_No_brackets) {
set_brackets({}, {});
} else if constexpr (sizeof...(_Types) == 2) {
if (_Fmt_type == _Fmt_tuple_type::_Key_value) {
set_separator(_STATICALLY_WIDEN(_CharT, ": "));
set_brackets({}, {});
}
}
_Ctx.advance_to(_Ctx.begin() + (_It - _Ctx._Unchecked_begin()));
_STD apply([&_Ctx](auto&... _Elems) { (_Set_tuple_debug_format(_Elems, _Ctx), ...); }, _Underlying);
return _Ctx.begin();
}
};
// formatter definition for all pairs and tuples, the deleted default constructor
// makes it "disabled" as per N4971 [format.formatter.spec]/5
template <class _TupleOrPair, class _CharT>
struct _Tuple_formatter_base {
_Tuple_formatter_base() = delete;
_Tuple_formatter_base(const _Tuple_formatter_base&) = delete;
_Tuple_formatter_base& operator=(const _Tuple_formatter_base&) = delete;
};
template <class _CharT, formattable<_CharT> _Ty1, formattable<_CharT> _Ty2>
struct _Tuple_formatter_base<pair<_Ty1, _Ty2>, _CharT> : _Tuple_formatter_common_base<_CharT, _Ty1, _Ty2> {
private:
using _Base = _Tuple_formatter_common_base<_CharT, _Ty1, _Ty2>;
using _Formatted_type = _Maybe_const<_Base::_Is_const_formattable, pair<_Ty1, _Ty2>>;
public:
template <class _FormatContext>
_FormatContext::iterator format(_Formatted_type& _Elems, _FormatContext& _Ctx) const {
return this->_Format(_Ctx, _Elems.first, _Elems.second);
}
};
template <class _CharT, formattable<_CharT>... _Types>
struct _Tuple_formatter_base<tuple<_Types...>, _CharT> : _Tuple_formatter_common_base<_CharT, _Types...> {
private:
using _Base = _Tuple_formatter_common_base<_CharT, _Types...>;
using _Formatted_type = _Maybe_const<_Base::_Is_const_formattable, tuple<_Types...>>;
public:
template <class _FormatContext>
_FormatContext::iterator format(_Formatted_type& _Elems, _FormatContext& _Ctx) const {
return _STD apply([this, &_Ctx](auto&... _Args) { return this->_Format(_Ctx, _Args...); }, _Elems);
}
};
// Per LWG-3997, `_CharT` in library-provided `formatter` specializations is
// constrained to character types supported by `format`.
template <_Format_supported_charT _CharT, class _Ty1, class _Ty2>
struct formatter<pair<_Ty1, _Ty2>, _CharT> : _Tuple_formatter_base<pair<_Ty1, _Ty2>, _CharT> {};
template <_Format_supported_charT _CharT, class... _Types>
struct formatter<tuple<_Types...>, _CharT> : _Tuple_formatter_base<tuple<_Types...>, _CharT> {};
enum class _Add_newline : bool { _Nope, _Yes };
_NODISCARD inline string _Unescape_braces(const _Add_newline _Add_nl, const string_view _Old_str) {
@ -3980,16 +4237,6 @@ _NODISCARD inline string _Unescape_braces(const _Add_newline _Add_nl, const stri
return _Unescaped_str;
}
template <class _CharT>
struct _Fill_align_and_width_specs { // used by thread::id and stacktrace_entry formatters
int _Width = -1;
int _Dynamic_width_index = -1;
_Fmt_align _Alignment = _Fmt_align::_None;
uint8_t _Fill_length = 1;
// At most one codepoint (so one char32_t or four utf-8 char8_t).
_CharT _Fill[4 / sizeof(_CharT)] = {' '};
};
template <class _CharT>
class _Fill_align_and_width_specs_setter {
public:

Просмотреть файл

@ -3578,7 +3578,9 @@ namespace pmr {
#endif // _HAS_CXX17
#if _HAS_CXX23
template <class _Ty, class _CharT>
// Per LWG-3997, `_CharT` in library-provided `formatter` specializations is
// constrained to character types supported by `format`.
template <class _Ty, _Format_supported_charT _CharT>
requires _Is_specialization_v<_Ty, _Vb_reference>
struct formatter<_Ty, _CharT> {
private:

Просмотреть файл

@ -33,6 +33,11 @@ std/utilities/memory/specialized.algorithms/uninitialized.move/uninitialized_mov
# LLVM-79783: [libc++][test] array/size_and_alignment.compile.pass.cpp includes non-Standard <__type_traits/datasizeof.h>
std/containers/sequences/array/size_and_alignment.compile.pass.cpp FAIL
# LLVM-83734: [libc++][test] Don't include test_format_context.h in parse.pass.cpp
std/containers/sequences/vector.bool/vector.bool.fmt/parse.pass.cpp FAIL
std/thread/thread.threads/thread.thread.class/thread.thread.id/parse.pass.cpp FAIL
std/utilities/format/format.tuple/parse.pass.cpp FAIL
# Non-Standard regex behavior.
# "It seems likely that the test is still non-conforming due to how libc++ handles the 'w' character class."
std/re/re.traits/lookup_classname.pass.cpp FAIL
@ -259,7 +264,6 @@ std/containers/container.adaptors/container.adaptors.format/types.compile.pass.c
std/containers/sequences/vector.bool/vector.bool.fmt/format.functions.format.pass.cpp FAIL
std/containers/sequences/vector.bool/vector.bool.fmt/format.functions.vformat.pass.cpp FAIL
std/containers/sequences/vector.bool/vector.bool.fmt/format.pass.cpp FAIL
std/containers/sequences/vector.bool/vector.bool.fmt/parse.pass.cpp FAIL
std/input.output/iostream.format/print.fun/includes.compile.pass.cpp FAIL
std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp FAIL
std/utilities/format/format.range/format.range.fmtdef/format.pass.cpp FAIL
@ -285,12 +289,6 @@ std/utilities/format/format.range/format.range.formatter/parse.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp FAIL
std/utilities/format/format.tuple/format.functions.format.pass.cpp FAIL
std/utilities/format/format.tuple/format.functions.vformat.pass.cpp FAIL
std/utilities/format/format.tuple/format.pass.cpp FAIL
std/utilities/format/format.tuple/parse.pass.cpp FAIL
std/utilities/format/format.tuple/set_brackets.pass.cpp FAIL
std/utilities/format/format.tuple/set_separator.pass.cpp FAIL
std/utilities/format/types.compile.pass.cpp FAIL
# P2363R5 Extending Associative Containers With The Remaining Heterogeneous Overloads
@ -1112,7 +1110,6 @@ std/input.output/string.streams/stringstream/stringstream.members/str.allocator_
# says "Please create a vendor specific version of the test functions".
# If we do, some of these tests should be able to work.
std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp FAIL
std/thread/thread.threads/thread.thread.class/thread.thread.id/parse.pass.cpp FAIL
std/utilities/format/format.formatter/format.context/format.context/advance_to.pass.cpp FAIL
std/utilities/format/format.formatter/format.context/format.context/arg.pass.cpp FAIL
std/utilities/format/format.formatter/format.context/format.context/ctor.pass.cpp FAIL
@ -1130,6 +1127,7 @@ std/utilities/format/format.formatter/format.formatter.spec/formatter.pointer.pa
std/utilities/format/format.formatter/format.formatter.spec/formatter.signed_integral.pass.cpp FAIL
std/utilities/format/format.formatter/format.formatter.spec/formatter.string.pass.cpp FAIL
std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp FAIL
std/utilities/format/format.tuple/format.pass.cpp FAIL
# Not analyzed. Apparent false positives from static analysis where it thinks that array indexing is out of bounds.
# warning C28020: The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call.

Просмотреть файл

@ -610,6 +610,7 @@ tests\P2286R8_text_formatting_escaping
tests\P2286R8_text_formatting_escaping_legacy_text_encoding
tests\P2286R8_text_formatting_escaping_utf8
tests\P2286R8_text_formatting_formattable
tests\P2286R8_text_formatting_tuple
tests\P2286R8_text_formatting_vector_bool_reference
tests\P2302R4_ranges_alg_contains
tests\P2302R4_ranges_alg_contains_subrange

Просмотреть файл

@ -213,6 +213,9 @@ void test_P2286_vector_bool() {
// Tests for P2286 Formatting ranges
template <class CharT>
void test_P2286() {
assert_is_formattable<pair<int, int>, CharT>();
assert_is_formattable<tuple<int>, CharT>();
test_P2286_vector_bool<CharT, vector<bool>>();
test_P2286_vector_bool<CharT, pmr::vector<bool>>();
test_P2286_vector_bool<CharT, vector<bool, alternative_allocator<bool>>>();

Просмотреть файл

@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
RUNALL_INCLUDE ..\usual_latest_matrix.lst

Просмотреть файл

@ -0,0 +1,437 @@
// 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:
// * std/utilities/format/format.tuple/format.functions.tests.h
// * std/utilities/format/format.tuple/format.functions.format.pass.cpp
// * std/utilities/format/format.tuple/format.functions.vformat.pass.cpp
#include <cassert>
#include <concepts>
#include <cstddef>
#include <format>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <test_format_support.hpp>
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))
enum class color { black, red, gold };
template <class CharT>
struct std::formatter<color, CharT> : std::formatter<basic_string_view<CharT>, CharT> {
static constexpr basic_string_view<CharT> color_names[] = {SV("black"), SV("red"), SV("gold")};
auto format(color c, auto& ctx) const {
return formatter<basic_string_view<CharT>, CharT>::format(color_names[static_cast<int>(c)], ctx);
}
};
//
// Generic tests for a tuple and pair with two elements.
//
template <class CharT, class TestFunction, class ExceptionTest, class TupleOrPair>
void test_tuple_or_pair_int_int(TestFunction check, ExceptionTest check_exception, TupleOrPair&& input) {
check(SV("(42, 99)"), SV("{}"), input);
check(SV("(42, 99)^42"), SV("{}^42"), input);
check(SV("(42, 99)^42"), SV("{:}^42"), input);
// *** align-fill & width ***
check(SV("(42, 99) "), SV("{:13}"), input);
check(SV("(42, 99)*****"), SV("{:*<13}"), input);
check(SV("__(42, 99)___"), SV("{:_^13}"), input);
check(SV("#####(42, 99)"), SV("{:#>13}"), input);
check(SV("(42, 99) "), SV("{:{}}"), input, 13);
check(SV("(42, 99)*****"), SV("{:*<{}}"), input, 13);
check(SV("__(42, 99)___"), SV("{:_^{}}"), input, 13);
check(SV("#####(42, 99)"), SV("{:#>{}}"), input, 13);
check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input);
check_exception("The fill option contains an invalid value", 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);
// *** type ***
check(SV("__42: 99___"), SV("{:_^11m}"), input);
check(SV("__42, 99___"), SV("{:_^11n}"), input);
for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) {
check_exception("The format specifier should consume the input or end with a '}'",
basic_string_view{STR("{:") + c + STR("}")}, input);
}
}
template <class CharT, class TestFunction, class ExceptionTest, class TupleOrPair>
void test_tuple_or_pair_int_string(TestFunction check, ExceptionTest check_exception, TupleOrPair&& input) {
check(SV("(42, \"hello\")"), SV("{}"), input);
check(SV("(42, \"hello\")^42"), SV("{}^42"), input);
check(SV("(42, \"hello\")^42"), SV("{:}^42"), input);
// *** align-fill & width ***
check(SV("(42, \"hello\") "), SV("{:18}"), input);
check(SV("(42, \"hello\")*****"), SV("{:*<18}"), input);
check(SV("__(42, \"hello\")___"), SV("{:_^18}"), input);
check(SV("#####(42, \"hello\")"), SV("{:#>18}"), input);
check(SV("(42, \"hello\") "), SV("{:{}}"), input, 18);
check(SV("(42, \"hello\")*****"), SV("{:*<{}}"), input, 18);
check(SV("__(42, \"hello\")___"), SV("{:_^{}}"), input, 18);
check(SV("#####(42, \"hello\")"), SV("{:#>{}}"), input, 18);
check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input);
check_exception("The fill option contains an invalid value", 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);
// *** type ***
check(SV("__42: \"hello\"___"), SV("{:_^16m}"), input);
check(SV("__42, \"hello\"___"), SV("{:_^16n}"), input);
for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) {
check_exception("The format specifier should consume the input or end with a '}'",
basic_string_view{STR("{:") + c + STR("}")}, input);
}
}
template <class CharT, class TestFunction, class TupleOrPair>
void test_escaping(TestFunction check, TupleOrPair&& input) {
static_assert(same_as<remove_cvref_t<decltype(get<0>(input))>, CharT>);
static_assert(same_as<remove_cvref_t<decltype(get<1>(input))>, basic_string<CharT>>);
check(SV(R"(('*', ""))"), SV("{}"), input);
// Char
get<0>(input) = CharT('\t');
check(SV(R"(('\t', ""))"), SV("{}"), input);
get<0>(input) = CharT('\n');
check(SV(R"(('\n', ""))"), SV("{}"), input);
get<0>(input) = CharT('\0');
check(SV(R"(('\u{0}', ""))"), SV("{}"), input);
// String
get<0>(input) = CharT('*');
get<1>(input) = SV("hell\u00d6");
check(SV("('*', \"hell\u00d6\")"), SV("{}"), input);
}
//
// pair tests
//
template <class CharT, class TestFunction, class ExceptionTest>
void test_pair_int_int(TestFunction check, ExceptionTest check_exception) {
test_tuple_or_pair_int_int<CharT>(check, check_exception, make_pair(42, 99));
}
template <class CharT, class TestFunction, class ExceptionTest>
void test_pair_int_string(TestFunction check, ExceptionTest check_exception) {
test_tuple_or_pair_int_string<CharT>(check, check_exception, make_pair(42, SV("hello")));
test_tuple_or_pair_int_string<CharT>(check, check_exception, make_pair(42, STR("hello")));
test_tuple_or_pair_int_string<CharT>(check, check_exception, make_pair(42, CSTR("hello")));
}
//
// tuple tests
//
template <class CharT, class TestFunction, class ExceptionTest>
void test_tuple_int(TestFunction check, ExceptionTest check_exception) {
auto input = make_tuple(42);
check(SV("(42)"), SV("{}"), input);
check(SV("(42)^42"), SV("{}^42"), input);
check(SV("(42)^42"), SV("{:}^42"), input);
// *** align-fill & width ***
check(SV("(42) "), SV("{:9}"), input);
check(SV("(42)*****"), SV("{:*<9}"), input);
check(SV("__(42)___"), SV("{:_^9}"), input);
check(SV("#####(42)"), SV("{:#>9}"), input);
check(SV("(42) "), SV("{:{}}"), input, 9);
check(SV("(42)*****"), SV("{:*<{}}"), input, 9);
check(SV("__(42)___"), SV("{:_^{}}"), input, 9);
check(SV("#####(42)"), SV("{:#>{}}"), input, 9);
check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input);
check_exception("The fill option contains an invalid value", 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);
// *** type ***
check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input);
check(SV("__42___"), SV("{:_^7n}"), input);
for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) {
check_exception("The format specifier should consume the input or end with a '}'",
basic_string_view{STR("{:") + c + STR("}")}, input);
}
}
template <class CharT, class TestFunction, class ExceptionTest>
void test_tuple_int_string_color(TestFunction check, ExceptionTest check_exception) {
const auto input = make_tuple(42, SV("hello"), color::red);
check(SV("(42, \"hello\", \"red\")"), SV("{}"), input);
check(SV("(42, \"hello\", \"red\")^42"), SV("{}^42"), input);
check(SV("(42, \"hello\", \"red\")^42"), SV("{:}^42"), input);
// *** align-fill & width ***
check(SV("(42, \"hello\", \"red\") "), SV("{:25}"), input);
check(SV("(42, \"hello\", \"red\")*****"), SV("{:*<25}"), input);
check(SV("__(42, \"hello\", \"red\")___"), SV("{:_^25}"), input);
check(SV("#####(42, \"hello\", \"red\")"), SV("{:#>25}"), input);
check(SV("(42, \"hello\", \"red\") "), SV("{:{}}"), input, 25);
check(SV("(42, \"hello\", \"red\")*****"), SV("{:*<{}}"), input, 25);
check(SV("__(42, \"hello\", \"red\")___"), SV("{:_^{}}"), input, 25);
check(SV("#####(42, \"hello\", \"red\")"), SV("{:#>{}}"), input, 25);
check_exception("The format string contains an invalid escape sequence", SV("{:}<}"), input);
check_exception("The fill option contains an invalid value", 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);
// *** type ***
check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input);
check(SV("__42, \"hello\", \"red\"___"), SV("{:_^23n}"), input);
for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) {
check_exception("The format specifier should consume the input or end with a '}'",
basic_string_view{STR("{:") + c + STR("}")}, input);
}
}
template <class CharT, class TestFunction, class ExceptionTest>
void test_tuple_int_int(TestFunction check, ExceptionTest check_exception) {
test_tuple_or_pair_int_int<CharT>(check, check_exception, make_tuple(42, 99));
}
template <class CharT, class TestFunction, class ExceptionTest>
void test_tuple_int_string(TestFunction check, ExceptionTest check_exception) {
test_tuple_or_pair_int_string<CharT>(check, check_exception, make_tuple(42, SV("hello")));
test_tuple_or_pair_int_string<CharT>(check, check_exception, make_tuple(42, STR("hello")));
test_tuple_or_pair_int_string<CharT>(check, check_exception, make_tuple(42, CSTR("hello")));
}
template <class CharT, class TestFunction, class ExceptionTest, class Nested>
void test_nested(TestFunction check, ExceptionTest check_exception, Nested&& input) {
// N4971 [format.formatter.spec]/2
// A debug-enabled specialization of formatter additionally provides a
// public, constexpr, non-static member function set_debug_format()
// which modifies the state of the formatter to be as if the type of the
// std-format-spec parsed by the last call to parse were ?.
// pair and tuple are not debug-enabled specializations so the
// set_debug_format is not propagated. The paper
// P2733 Fix handling of empty specifiers in format
// addressed this.
check(SV("(42, (\"hello\", \"red\"))"), SV("{}"), input);
check(SV("(42, (\"hello\", \"red\"))^42"), SV("{}^42"), input);
check(SV("(42, (\"hello\", \"red\"))^42"), SV("{:}^42"), input);
// *** align-fill & width ***
check(SV("(42, (\"hello\", \"red\")) "), SV("{:27}"), input);
check(SV("(42, (\"hello\", \"red\"))*****"), SV("{:*<27}"), input);
check(SV("__(42, (\"hello\", \"red\"))___"), SV("{:_^27}"), input);
check(SV("#####(42, (\"hello\", \"red\"))"), SV("{:#>27}"), input);
check(SV("(42, (\"hello\", \"red\")) "), SV("{:{}}"), input, 27);
check(SV("(42, (\"hello\", \"red\"))*****"), SV("{:*<{}}"), input, 27);
check(SV("__(42, (\"hello\", \"red\"))___"), SV("{:_^{}}"), input, 27);
check(SV("#####(42, (\"hello\", \"red\"))"), 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);
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);
// *** type ***
check(SV("__42: (\"hello\", \"red\")___"), SV("{:_^25m}"), input);
check(SV("__42, (\"hello\", \"red\")___"), SV("{:_^25n}"), input);
for (CharT c : SV("aAbBcdeEfFgGopPsxX?")) {
check_exception("The format specifier should consume the input or end with a '}'",
basic_string_view{STR("{:") + c + STR("}")}, input);
}
}
template <class CharT, class TestFunction, class ExceptionTest>
void run_tests(TestFunction check, ExceptionTest check_exception) {
test_pair_int_int<CharT>(check, check_exception);
test_pair_int_string<CharT>(check, check_exception);
test_tuple_int<CharT>(check, check_exception);
test_tuple_int_int<CharT>(check, check_exception);
test_tuple_int_string<CharT>(check, check_exception);
test_tuple_int_string_color<CharT>(check, check_exception);
test_nested<CharT>(check, check_exception, make_pair(42, make_pair(SV("hello"), color::red)));
test_nested<CharT>(check, check_exception, make_pair(42, make_tuple(SV("hello"), color::red)));
test_nested<CharT>(check, check_exception, make_tuple(42, make_pair(SV("hello"), color::red)));
test_nested<CharT>(check, check_exception, make_tuple(42, make_tuple(SV("hello"), color::red)));
test_escaping<CharT>(check, make_pair(CharT('*'), STR("")));
test_escaping<CharT>(check, make_tuple(CharT('*'), STR("")));
// Test const ref-qualified types.
// clang-format off
check(SV("(42)"), SV("{}"), tuple< int >{42});
check(SV("(42)"), SV("{}"), tuple<const int >{42});
int answer = 42;
check(SV("(42)"), SV("{}"), tuple< int& >{answer});
check(SV("(42)"), SV("{}"), tuple<const int& >{answer});
check(SV("(42)"), SV("{}"), tuple< int&&>{42});
check(SV("(42)"), SV("{}"), tuple<const int&&>{42});
// clang-format on
}
template <class>
struct format_context_for_impl {};
template <>
struct format_context_for_impl<char> {
using type = format_context;
};
template <>
struct format_context_for_impl<wchar_t> {
using type = wformat_context;
};
template <class CharT>
using format_context_for = format_context_for_impl<CharT>::type;
auto test_format = []<class CharT, class... Args>(basic_string_view<CharT> expected,
type_identity_t<basic_format_string<CharT, Args...>> fmt, Args&&... args) {
auto out = format(fmt, forward<Args>(args)...);
assert(out == expected);
};
auto test_format_exception = []<class CharT, class... Args>(string_view, basic_string_view<CharT>, Args&&...) {
// After P2216 most exceptions thrown by format become ill-formed.
// Therefore this test does nothing.
// A basic ill-formed test is done in format.verify.cpp
// The exceptions are tested by other functions that don't use the basic-format-string as fmt argument.
};
auto test_vformat = []<class CharT, class... Args>(
basic_string_view<CharT> expected, basic_string_view<CharT> fmt, Args&&... args) {
auto out = vformat(fmt, make_format_args<format_context_for<CharT>>(args...));
assert(out == expected);
};
auto test_vformat_exception = []<class CharT, class... Args>([[maybe_unused]] string_view what,
[[maybe_unused]] basic_string_view<CharT> fmt, [[maybe_unused]] Args&&... args) {
try {
(void) vformat(fmt, make_format_args<format_context_for<CharT>>(args...));
assert(false);
} catch (const format_error&) {
}
};
int main() {
run_tests<char>(test_format, test_format_exception);
run_tests<char>(test_vformat, test_vformat_exception);
run_tests<wchar_t>(test_format, test_format_exception);
run_tests<wchar_t>(test_vformat, test_vformat_exception);
}