This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

### 3636. `formatter<T>::format` should be `const`-qualified

Section: 20.20.6.1 [formatter.requirements] Status: New Submitter: Arthur O'Dwyer Opened: 2021-11-11 Last modified: 2021-11-20

Priority: 1

View all other issues in [formatter.requirements].

View all issues with New status.

Discussion:

In libc++ review, we've noticed that we don't understand the implications of 20.20.6.1 [formatter.requirements] bullet 3.1 and Table [tab:formatter.basic]: (emphasize mine):

> (3.1) — `f` is a value of type `F`,

> […]

> Table 70: BasicFormatter requirements [tab:formatter.basic]

> […]

> `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.

That is, a typical formatter should look something like this (modulo errors introduced by me):

>     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.

Proposed resolution:

This wording is relative to N4901.

1. Modify 20.20.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 in `g`." (But I don't think anyone's seriously confused by that wording.)

   > -3- Given character type `charT`, output iterator type `Out`, and formatting argument type `T`, in Table 70 and Table 71:

   > 1. (3.1) — `f` is a value of type (possibly `const`) `F`,

   > 2. (3.?) — `g` is an lvalue of type `F`,

   > 3. (3.2) — `u` is an lvalue of type `T`,

   > 4. (3.3) — `t` is a value of a type convertible to (possibly `const`) `T`,

   > 5. […]

   > […]

   > > 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.
   > > `…`

2. Modify 20.20.6.2 [format.formatter.spec] as indicated:

   > -6- An enabled specialization `formatter<T, charT>` meets the BasicFormatter requirements (20.20.6.1 [formatter.requirements]).

   > [Example 1:

   > >     #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]

3. Modify 20.20.6.4 [format.context] as indicated:

   >     void advance_to(iterator it);

   > > -8- Effects: Equivalent to: `out_ = std::move(it);`

   > > [Example 1:

   > > >     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]

4. Modify 27.12 [time.format] as indicated:

   >     template<class Duration, class charT>
   >     struct formatter<chrono::local-time-format-t<Duration>, charT>;

   > > -15- Let `f` be […]

   > > -16- Remarks: […]

   > >     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);