Skip to content

Commit

Permalink
Implement formatter specializations for pair and tuple (#4438)
Browse files Browse the repository at this point in the history
Co-authored-by: Stephan T. Lavavej <[email protected]>
  • Loading branch information
frederick-vs-ja and StephanTLavavej authored Mar 16, 2024
1 parent 4d088fc commit 79e79a2
Show file tree
Hide file tree
Showing 9 changed files with 734 additions and 23 deletions.
2 changes: 1 addition & 1 deletion docs/cgmanifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"type": "git",
"git": {
"repositoryUrl": "https://github.com/llvm/llvm-project.git",
"commitHash": "b8d38e8b4fcab071c5c4cb698e154023d06de69e"
"commitHash": "2e2b6b53f5f63179b52168ee156df7c76b90bc71"
}
}
},
Expand Down
25 changes: 22 additions & 3 deletions stl/inc/__msvc_formatter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
267 changes: 257 additions & 10 deletions stl/inc/format
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion stl/inc/vector
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 6 additions & 8 deletions tests/libcxx/expected_results.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
Loading

0 comments on commit 79e79a2

Please sign in to comment.