Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<format>: Implement P2418R2 support for std::generator-like types #2323

Merged
merged 14 commits into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>;

StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
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);
};
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

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));
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
}) {
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&&) {
// LWG-3631
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -2946,12 +2957,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 @@ -3012,22 +3023,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 @@ -3074,22 +3085,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 @@ -3100,60 +3111,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 @@ -1279,7 +1280,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()) {
CaseyCarter marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -992,32 +992,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)...);
CaseyCarter marked this conversation as resolved.
Show resolved Hide resolved
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