formatter<T>::format
should be const
-qualifiedSection: 28.5.6.1 [formatter.requirements] Status: C++23 Submitter: Arthur O'Dwyer Opened: 2021-11-11 Last modified: 2023-11-22
Priority: 1
View all other issues in [formatter.requirements].
View all issues with C++23 status.
Discussion:
In libc++ review, we've noticed that we don't understand the implications of 28.5.6.1 [formatter.requirements] bullet 3.1 and Table [tab:formatter.basic]: (emphasize mine):
(3.1) —
[…] Table 70: BasicFormatter requirements [tab:formatter.basic] […]f
is a value of typeF
,f.parse(pc)
[must compile] […]f.format(u, fc)
[must compile] […]
According to Victor Zverovich, his intent was that f.parse(pc)
should modify the
state of f
, but f.format(u, fc)
should merely read f
's state to
support format string compilation where formatter objects are immutable and therefore the
format
function must be const
-qualified.
struct WidgetFormatter { auto parse(std::format_parse_context&) -> std::format_parse_context::iterator; auto format(const Widget&, std::format_context&) const -> std::format_context::iterator; };
However, this is not reflected in the wording, which treats parse
and format
symmetrically.
Also, there is at least one example that shows a non-const format
method:
template<> struct std::formatter<color> : std::formatter<const char*> { auto format(color c, format_context& ctx) { return formatter<const char*>::format(color_names[c], ctx); } };
Victor writes:
Maybe we should […] open an LWG issue clarifying that all standard formatters have a
const
format function.
I'd like to be even more assertive: Let's open an LWG issue clarifying that all formatters must have a
const
format function!
[2022-01-30; Reflector poll]
Set priority to 1 after reflector poll.
[2022-08-24 Approved unanimously in LWG telecon.]
[2022-11-12 Approved at November 2022 meeting in Kona. Status changed: Voting → WP.]
Proposed resolution:
This wording is relative to N4901.
Modify 28.5.6.1 [formatter.requirements] as indicated:
[Drafting note: It might also be reasonable to do a drive-by clarification that when the Table 70 says "Stores the parsed format specifiers in
*this
," what it actually means is "Stores the parsed format specifiers ing
." (But I don't think anyone's seriously confused by that wording.)
-3- Given character type
charT
, output iterator typeOut
, and formatting argument typeT
, in Table 70 and Table 71:
(3.1) —
f
is a value of type (possiblyconst
)F
,(3.?) —
g
is an lvalue of typeF
,(3.2) —
u
is an lvalue of typeT
,(3.3) —
t
is a value of a type convertible to (possiblyconst
)T
,[…]
[…]
Table 70: Formatter requirements [tab:formatter] Expression Return type Requirement
fg.parse(pc)PC::iterator
[…]
Stores the parsed format specifiers in*this
and returns an iterator past the end of the parsed range.…
Modify 28.5.6.4 [format.formatter.spec] as indicated:
-6- An enabled specialization
[Example 1:formatter<T, charT>
meets the BasicFormatter requirements (28.5.6.1 [formatter.requirements]).#include <format> enum color { red, green, blue }; const char* color_names[] = { "red", "green", "blue" }; template<> struct std::formatter<color> : std::formatter<const char*> { auto format(color c, format_context& ctx) const { return formatter<const char*>::format(color_names[c], ctx); } }; […]— end example]
Modify 28.5.6.7 [format.context] as indicated:
void advance_to(iterator it);-8- Effects: Equivalent to:
[Example 1:out_ = std::move(it);
struct S { int value; }; template<> struct std::formatter<S> { size_t width_arg_id = 0; // Parses a width argument id in the format { digit }. constexpr auto parse(format_parse_context& ctx) { […] } // Formats an S with width given by the argument width_arg_id. auto format(S s, format_context& ctx) const { int width = visit_format_arg([](auto value) -> int { if constexpr (!is_integral_v<decltype(value)>) throw format_error("width is not integral"); else if (value < 0 || value > numeric_limits<int>::max()) throw format_error("invalid width"); else return value; }, ctx.arg(width_arg_id)); return format_to(ctx.out(), "{0:x<{1}}", s.value, width); } }; […]— end example]
Modify 30.12 [time.format] as indicated:
template<class Duration, class charT> struct formatter<chrono::local-time-format-t<Duration>, charT>;-15- Let
-16- Remarks: […]f
be […]template<class Duration, class TimeZonePtr, class charT> struct formatter<chrono::zoned_time<Duration, TimeZonePtr>, charT> : formatter<chrono::local-time-format-t<Duration>, charT> { template<class FormatContext> typename FormatContext::iterator format(const chrono::zoned_time<Duration, TimeZonePtr>& tp, FormatContext& ctx) const; };template<class FormatContext> typename FormatContext::iterator format(const chrono::zoned_time<Duration, TimeZonePtr>& tp, FormatContext& ctx) const;-17- Effects: Equivalent to:
sys_info info = tp.get_info(); return formatter<chrono::local-time-format-t<Duration>, charT>:: format({tp.get_local_time(), &info.abbrev, &info.offset}, ctx);