This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++23 status.
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] […]fis 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
constformat 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) —
fis a value of type (possiblyconst)F,(3.?) —
gis an lvalue of typeF,(3.2) —
uis an lvalue of typeT,(3.3) —
tis 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*thisand 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: […]fbe […]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);