Skip to content

Commit

Permalink
<format>: Implement P2418R2 support for std::generator-like types (
Browse files Browse the repository at this point in the history
…#2323)

Co-authored-by: Stephan T. Lavavej <[email protected]>
  • Loading branch information
barcharcraz and StephanTLavavej authored Jan 26, 2022
1 parent 59a87cc commit f0c0eaa
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 54 deletions.
95 changes: 53 additions & 42 deletions stl/inc/format
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ concept _CharT_or_bool = same_as<_Ty, _CharT> || same_as<_Ty, bool>;
template <class _CharT>
concept _Format_supported_charT = _Is_any_of_v<_CharT, char, wchar_t>;

template <class _Ty, class _Context>
concept _Has_formatter = requires(_Ty& _Val, _Context& _Ctx) {
_STD declval<typename _Context::template formatter_type<remove_cvref_t<_Ty>>>().format(_Val, _Ctx);
};

template <class _Ty, class _Context>
concept _Has_const_formatter = _Has_formatter<const remove_reference_t<_Ty>, _Context>;

template <class _Ty, class _CharT = char>
struct formatter;

Expand Down Expand Up @@ -265,13 +273,19 @@ public:

public:
template <class _Ty>
explicit handle(const _Ty& _Val) noexcept
explicit handle(_Ty&& _Val) noexcept
: _Ptr(_STD addressof(_Val)),
_Format([](basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void* _Ptr) {
typename _Context::template formatter_type<_Ty> _Formatter;
using _Value_type = remove_cvref_t<_Ty>;
typename _Context::template formatter_type<_Value_type> _Formatter;
using _Qualified_type =
conditional_t<_Has_const_formatter<_Value_type, _Context>, const _Value_type, _Value_type>;
_Parse_ctx.advance_to(_Formatter.parse(_Parse_ctx));
_Format_ctx.advance_to(_Formatter.format(*static_cast<const _Ty*>(_Ptr), _Format_ctx));
}) {}
_Format_ctx.advance_to(_Formatter.format(
*const_cast<_Qualified_type*>(static_cast<const _Value_type*>(_Ptr)), _Format_ctx));
}) {
static_assert(_Has_const_formatter<_Ty, _Context> || !is_const_v<remove_reference_t<_Ty>>);
}

void format(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx) const {
_Format(_Parse_ctx, _Format_ctx, _Ptr);
Expand Down Expand Up @@ -1402,11 +1416,6 @@ public:
}
};

template <class _Ty, class _Context>
concept _Has_formatter = requires(const _Ty& _Val, _Context& _Ctx) {
_STD declval<typename _Context::template formatter_type<_Ty>>().format(_Val, _Ctx);
};

template <class _Context>
struct _Format_arg_traits {
using _Char_type = typename _Context::char_type;
Expand All @@ -1415,21 +1424,23 @@ struct _Format_arg_traits {
// set of basic_format_arg (N4885 [format.arg]). They determine the mapping
// from "raw" to "erased" argument type for _Format_arg_store.
template <_Has_formatter<_Context> _Ty>
static auto _Phony_basic_format_arg_constructor(const _Ty&) {
static auto _Phony_basic_format_arg_constructor(_Ty&&) {
// per the proposed resolution of LWG-3631
using _Td = remove_cvref_t<_Ty>;
// See N4885 [format.arg]/5
if constexpr (is_same_v<_Ty, bool>) {
if constexpr (is_same_v<_Td, bool>) {
return bool{};
} else if constexpr (is_same_v<_Ty, _Char_type>) {
} else if constexpr (is_same_v<_Td, _Char_type>) {
return _Char_type{};
} else if constexpr (is_same_v<_Ty, char> && is_same_v<_Char_type, wchar_t>) {
} else if constexpr (is_same_v<_Td, char> && is_same_v<_Char_type, wchar_t>) {
return _Char_type{};
} else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(int)) {
} else if constexpr (signed_integral<_Td> && sizeof(_Td) <= sizeof(int)) {
return int{};
} else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned int)) {
} else if constexpr (unsigned_integral<_Td> && sizeof(_Td) <= sizeof(unsigned int)) {
return static_cast<unsigned int>(42);
} else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(long long)) {
} else if constexpr (signed_integral<_Td> && sizeof(_Td) <= sizeof(long long)) {
return static_cast<long long>(42);
} else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned long long)) {
} else if constexpr (unsigned_integral<_Td> && sizeof(_Td) <= sizeof(unsigned long long)) {
return static_cast<unsigned long long>(42);
} else {
return typename basic_format_arg<_Context>::handle{42};
Expand Down Expand Up @@ -1459,10 +1470,10 @@ struct _Format_arg_traits {
// clang-format on

template <class _Ty>
using _Storage_type = decltype(_Phony_basic_format_arg_constructor(_STD declval<const _Ty&>()));
using _Storage_type = decltype(_Phony_basic_format_arg_constructor(_STD declval<_Ty>()));

template <class _Ty>
static constexpr size_t _Storage_size = sizeof(_Storage_type<_Ty>);
static constexpr size_t _Storage_size = sizeof(_Storage_type<remove_cvref_t<_Ty>>);
};

struct _Format_arg_index {
Expand Down Expand Up @@ -1523,8 +1534,8 @@ private:
}

template <class _Ty>
void _Store(const size_t _Arg_index, const _Ty& _Val) noexcept {
using _Erased_type = typename _Traits::template _Storage_type<_Ty>;
void _Store(const size_t _Arg_index, _Ty&& _Val) noexcept {
using _Erased_type = typename _Traits::template _Storage_type<remove_cvref_t<_Ty>>;

_Basic_format_arg_type _Arg_type;
if constexpr (is_same_v<_Erased_type, bool>) {
Expand Down Expand Up @@ -1560,7 +1571,7 @@ private:
}

public:
_Format_arg_store(const _Args&... _Vals) noexcept {
_Format_arg_store(_Args&... _Vals) noexcept {
_Index_array[0] = {};
size_t _Arg_index = 0;
(_Store(_Arg_index++, _Vals), ...);
Expand Down Expand Up @@ -2948,12 +2959,12 @@ using format_args = basic_format_args<format_context>;
using wformat_args = basic_format_args<wformat_context>;

template <class _Context = format_context, class... _Args>
_NODISCARD auto make_format_args(const _Args&... _Vals) {
_NODISCARD auto make_format_args(_Args&&... _Vals) {
return _Format_arg_store<_Context, _Args...>{_Vals...};
}

template <class... _Args>
_NODISCARD auto make_wformat_args(const _Args&... _Vals) {
_NODISCARD auto make_wformat_args(_Args&&... _Vals) {
return _Format_arg_store<wformat_context, _Args...>{_Vals...};
}

Expand Down Expand Up @@ -3014,22 +3025,22 @@ _OutputIt vformat_to(_OutputIt _Out, const locale& _Loc, const wstring_view _Fmt
}

template <output_iterator<const char&> _OutputIt, class... _Types>
_OutputIt format_to(_OutputIt _Out, const _Fmt_string<_Types...> _Fmt, const _Types&... _Args) {
_OutputIt format_to(_OutputIt _Out, const _Fmt_string<_Types...> _Fmt, _Types&&... _Args) {
return _STD vformat_to(_STD move(_Out), _Fmt._Str, _STD make_format_args(_Args...));
}

template <output_iterator<const wchar_t&> _OutputIt, class... _Types>
_OutputIt format_to(_OutputIt _Out, const _Fmt_wstring<_Types...> _Fmt, const _Types&... _Args) {
_OutputIt format_to(_OutputIt _Out, const _Fmt_wstring<_Types...> _Fmt, _Types&&... _Args) {
return _STD vformat_to(_STD move(_Out), _Fmt._Str, _STD make_wformat_args(_Args...));
}

template <output_iterator<const char&> _OutputIt, class... _Types>
_OutputIt format_to(_OutputIt _Out, const locale& _Loc, const _Fmt_string<_Types...> _Fmt, const _Types&... _Args) {
_OutputIt format_to(_OutputIt _Out, const locale& _Loc, const _Fmt_string<_Types...> _Fmt, _Types&&... _Args) {
return _STD vformat_to(_STD move(_Out), _Loc, _Fmt._Str, _STD make_format_args(_Args...));
}

template <output_iterator<const wchar_t&> _OutputIt, class... _Types>
_OutputIt format_to(_OutputIt _Out, const locale& _Loc, const _Fmt_wstring<_Types...> _Fmt, const _Types&... _Args) {
_OutputIt format_to(_OutputIt _Out, const locale& _Loc, const _Fmt_wstring<_Types...> _Fmt, _Types&&... _Args) {
return _STD vformat_to(_STD move(_Out), _Loc, _Fmt._Str, _STD make_wformat_args(_Args...));
}

Expand Down Expand Up @@ -3076,22 +3087,22 @@ wstring vformat(const locale& _Loc, const wstring_view _Fmt, const wformat_args
#undef _TEMPLATE_INT_0_NODISCARD // TRANSITION, VSO-1433873

template <class... _Types>
_NODISCARD string format(const _Fmt_string<_Types...> _Fmt, const _Types&... _Args) {
_NODISCARD string format(const _Fmt_string<_Types...> _Fmt, _Types&&... _Args) {
return _STD vformat(_Fmt._Str, _STD make_format_args(_Args...));
}

template <class... _Types>
_NODISCARD wstring format(const _Fmt_wstring<_Types...> _Fmt, const _Types&... _Args) {
_NODISCARD wstring format(const _Fmt_wstring<_Types...> _Fmt, _Types&&... _Args) {
return _STD vformat(_Fmt._Str, _STD make_wformat_args(_Args...));
}

template <class... _Types>
_NODISCARD string format(const locale& _Loc, const _Fmt_string<_Types...> _Fmt, const _Types&... _Args) {
_NODISCARD string format(const locale& _Loc, const _Fmt_string<_Types...> _Fmt, _Types&&... _Args) {
return _STD vformat(_Loc, _Fmt._Str, _STD make_format_args(_Args...));
}

template <class... _Types>
_NODISCARD wstring format(const locale& _Loc, const _Fmt_wstring<_Types...> _Fmt, const _Types&... _Args) {
_NODISCARD wstring format(const locale& _Loc, const _Fmt_wstring<_Types...> _Fmt, _Types&&... _Args) {
return _STD vformat(_Loc, _Fmt._Str, _STD make_wformat_args(_Args...));
}

Expand All @@ -3102,60 +3113,60 @@ struct format_to_n_result {
};

template <output_iterator<const char&> _OutputIt, class... _Types>
format_to_n_result<_OutputIt> format_to_n(_OutputIt _Out, const iter_difference_t<_OutputIt> _Max,
const _Fmt_string<_Types...> _Fmt, const _Types&... _Args) {
format_to_n_result<_OutputIt> format_to_n(
_OutputIt _Out, const iter_difference_t<_OutputIt> _Max, const _Fmt_string<_Types...> _Fmt, _Types&&... _Args) {
_Fmt_iterator_buffer<_OutputIt, char, _Fmt_fixed_buffer_traits> _Buf(_STD move(_Out), _Max);
_STD vformat_to(_Fmt_it{_Buf}, _Fmt._Str, _STD make_format_args(_Args...));
return {.out = _Buf._Out(), .size = _Buf._Count()};
}

template <output_iterator<const wchar_t&> _OutputIt, class... _Types>
format_to_n_result<_OutputIt> format_to_n(_OutputIt _Out, const iter_difference_t<_OutputIt> _Max,
const _Fmt_wstring<_Types...> _Fmt, const _Types&... _Args) {
format_to_n_result<_OutputIt> format_to_n(
_OutputIt _Out, const iter_difference_t<_OutputIt> _Max, const _Fmt_wstring<_Types...> _Fmt, _Types&&... _Args) {
_Fmt_iterator_buffer<_OutputIt, wchar_t, _Fmt_fixed_buffer_traits> _Buf(_STD move(_Out), _Max);
_STD vformat_to(_Fmt_wit{_Buf}, _Fmt._Str, _STD make_wformat_args(_Args...));
return {.out = _Buf._Out(), .size = _Buf._Count()};
}

template <output_iterator<const char&> _OutputIt, class... _Types>
format_to_n_result<_OutputIt> format_to_n(_OutputIt _Out, const iter_difference_t<_OutputIt> _Max, const locale& _Loc,
const _Fmt_string<_Types...> _Fmt, const _Types&... _Args) {
const _Fmt_string<_Types...> _Fmt, _Types&&... _Args) {
_Fmt_iterator_buffer<_OutputIt, char, _Fmt_fixed_buffer_traits> _Buf(_STD move(_Out), _Max);
_STD vformat_to(_Fmt_it{_Buf}, _Loc, _Fmt._Str, _STD make_format_args(_Args...));
return {.out = _Buf._Out(), .size = _Buf._Count()};
}

template <output_iterator<const wchar_t&> _OutputIt, class... _Types>
format_to_n_result<_OutputIt> format_to_n(_OutputIt _Out, const iter_difference_t<_OutputIt> _Max, const locale& _Loc,
const _Fmt_wstring<_Types...> _Fmt, const _Types&... _Args) {
const _Fmt_wstring<_Types...> _Fmt, _Types&&... _Args) {
_Fmt_iterator_buffer<_OutputIt, wchar_t, _Fmt_fixed_buffer_traits> _Buf(_STD move(_Out), _Max);
_STD vformat_to(_Fmt_wit{_Buf}, _Loc, _Fmt._Str, _STD make_wformat_args(_Args...));
return {.out = _Buf._Out(), .size = _Buf._Count()};
}

template <class... _Types>
_NODISCARD size_t formatted_size(const _Fmt_string<_Types...> _Fmt, const _Types&... _Args) {
_NODISCARD size_t formatted_size(const _Fmt_string<_Types...> _Fmt, _Types&&... _Args) {
_Fmt_counting_buffer<char> _Buf;
_STD vformat_to(_Fmt_it{_Buf}, _Fmt._Str, _STD make_format_args(_Args...));
return _Buf._Count();
}

template <class... _Types>
_NODISCARD size_t formatted_size(const _Fmt_wstring<_Types...> _Fmt, const _Types&... _Args) {
_NODISCARD size_t formatted_size(const _Fmt_wstring<_Types...> _Fmt, _Types&&... _Args) {
_Fmt_counting_buffer<wchar_t> _Buf;
_STD vformat_to(_Fmt_wit{_Buf}, _Fmt._Str, _STD make_wformat_args(_Args...));
return _Buf._Count();
}

template <class... _Types>
_NODISCARD size_t formatted_size(const locale& _Loc, const _Fmt_string<_Types...> _Fmt, const _Types&... _Args) {
_NODISCARD size_t formatted_size(const locale& _Loc, const _Fmt_string<_Types...> _Fmt, _Types&&... _Args) {
_Fmt_counting_buffer<char> _Buf;
_STD vformat_to(_Fmt_it{_Buf}, _Loc, _Fmt._Str, _STD make_format_args(_Args...));
return _Buf._Count();
}

template <class... _Types>
_NODISCARD size_t formatted_size(const locale& _Loc, const _Fmt_wstring<_Types...> _Fmt, const _Types&... _Args) {
_NODISCARD size_t formatted_size(const locale& _Loc, const _Fmt_wstring<_Types...> _Fmt, _Types&&... _Args) {
_Fmt_counting_buffer<wchar_t> _Buf;
_STD vformat_to(_Fmt_wit{_Buf}, _Loc, _Fmt._Str, _STD make_wformat_args(_Args...));
return _Buf._Count();
Expand Down
3 changes: 2 additions & 1 deletion stl/inc/yvals_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@
// P2367R0 Remove Misuses Of List-Initialization From Clause 24 Ranges
// P2372R3 Fixing Locale Handling In chrono Formatters
// P2415R2 What Is A view?
// P2418R2 Add Support For std::generator-like Types To std::format
// P2432R1 Fix istream_view
// P????R? directory_entry::clear_cache()

Expand Down Expand Up @@ -1291,7 +1292,7 @@
#define __cpp_lib_erase_if 202002L

#if _HAS_CXX23 && defined(__cpp_lib_concepts) // TRANSITION, GH-395 and GH-1814
#define __cpp_lib_format 202106L // P2216R3 std::format Improvements
#define __cpp_lib_format 202110L
#endif // _HAS_CXX23 && defined(__cpp_lib_concepts)

#define __cpp_lib_generic_unordered_lookup 201811L
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
#include <algorithm>
#include <assert.h>
#include <format>
#include <iterator>
#include <limits>
#include <locale>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
using namespace std;

// copied from the text_formatting_formatting test case
Expand Down Expand Up @@ -45,6 +48,13 @@ struct basic_custom_formattable_type {
string_view string_content;
};

struct not_const_formattable_type {
string_view string_content;
explicit not_const_formattable_type(string_view val) : string_content(val) {}
not_const_formattable_type(const not_const_formattable_type&) = delete;
not_const_formattable_type(not_const_formattable_type&&) = delete;
};

template <>
struct std::formatter<basic_custom_formattable_type, char> {
basic_format_parse_context<char>::iterator parse(basic_format_parse_context<char>& parse_ctx) {
Expand All @@ -59,6 +69,20 @@ struct std::formatter<basic_custom_formattable_type, char> {
}
};

template <>
struct std::formatter<not_const_formattable_type, char> {
basic_format_parse_context<char>::iterator parse(basic_format_parse_context<char>& parse_ctx) {
if (parse_ctx.begin() != parse_ctx.end()) {
throw format_error{"only empty specs please"};
}
return parse_ctx.end();
}
format_context::iterator format(not_const_formattable_type& val, format_context& ctx) {
ctx.advance_to(copy(val.string_content.begin(), val.string_content.end(), ctx.out()));
return ctx.out();
}
};

template <class T>
struct custom_formattable_type {
T val;
Expand Down Expand Up @@ -123,6 +147,28 @@ void test_numeric_mixed_args_custom_formattable_type() {
}
}

template <class CustomFormattableType>
void test_format_family_overloads() {
string str;

assert(format("{}", CustomFormattableType{"f"}) == "f"s);
assert(format(locale{}, "{}", CustomFormattableType{"f"}) == "f"s);
format_to(back_insert_iterator(str), "{}", CustomFormattableType{"f"});
assert(str == "f");
str.clear();
format_to(back_insert_iterator(str), locale{}, "{}", CustomFormattableType{"f"});
assert(str == "f");
str.clear();
format_to_n(back_insert_iterator(str), 5, "{}", CustomFormattableType{"f"});
assert(str == "f");
str.clear();
format_to_n(back_insert_iterator(str), 5, locale{}, "{}", CustomFormattableType{"f"});
assert(str == "f");
str.clear();
assert(formatted_size("{}", CustomFormattableType{"f"}) == 1);
assert(formatted_size(locale{}, "{}", CustomFormattableType{"f"}) == 1);
}

template <class charT>
void test_custom_formattable_type() {
test_numeric_custom_formattable_type<int, charT>();
Expand Down Expand Up @@ -152,7 +198,8 @@ void test_mixed_custom_formattable_type() {
}

int main() {
assert(format("{}", basic_custom_formattable_type{"f"}) == "f"s);
test_format_family_overloads<basic_custom_formattable_type>();
test_format_family_overloads<not_const_formattable_type>();
test_custom_formattable_type<char>();
test_custom_formattable_type<wchar_t>();
test_mixed_custom_formattable_type<char>();
Expand Down
13 changes: 6 additions & 7 deletions tests/std/tests/P0645R10_text_formatting_formatting/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1017,32 +1017,31 @@ void test_spec_replacement_field() {
test_string_specs<charT>();
}
template <class charT, class... Args>
void test_size_helper_impl(
const size_t expected_size, const _Basic_format_string<charT, Args...> fmt, const Args&... args) {
assert(formatted_size(fmt, args...) == expected_size);
assert(formatted_size(locale::classic(), fmt, args...) == expected_size);
void test_size_helper_impl(const size_t expected_size, const _Basic_format_string<charT, Args...> fmt, Args&&... args) {
assert(formatted_size(fmt, forward<Args>(args)...) == expected_size);
assert(formatted_size(locale::classic(), fmt, forward<Args>(args)...) == expected_size);

const auto signed_size = static_cast<ptrdiff_t>(expected_size);
basic_string<charT> str;
{
str.resize(expected_size);
const auto res = format_to_n(str.begin(), signed_size, fmt, args...);
const auto res = format_to_n(str.begin(), signed_size, fmt, forward<Args>(args)...);
assert(res.size == signed_size);
assert(res.out - str.begin() == signed_size);
assert(res.out == str.end());
assert(vformat(fmt._Str, make_testing_format_args<charT>(args...)) == str);

basic_string<charT> locale_str;
locale_str.resize(expected_size);
format_to_n(locale_str.begin(), signed_size, locale::classic(), fmt, args...);
format_to_n(locale_str.begin(), signed_size, locale::classic(), fmt, forward<Args>(args)...);
assert(str == locale_str);
assert(locale_str.size() == expected_size);
}
basic_string<charT> half_str;
{
const auto half_size = expected_size / 2;
half_str.resize(half_size);
const auto res = format_to_n(half_str.begin(), static_cast<ptrdiff_t>(half_size), fmt, args...);
const auto res = format_to_n(half_str.begin(), static_cast<ptrdiff_t>(half_size), fmt, forward<Args>(args)...);
assert(res.size == signed_size);
assert(static_cast<size_t>(res.out - half_str.begin()) == half_size);
assert(res.out == half_str.end());
Expand Down
Loading

0 comments on commit f0c0eaa

Please sign in to comment.