Title
formatter<T>::format should be const-qualified
Status
c++23
Section
[formatter.requirements]
Submitter
Arthur O'Dwyer

Created on 2021-11-11.00:00:00 last changed 5 months ago

Messages

Date: 2022-11-17.00:42:33

Proposed resolution:

This wording is relative to N4901.

  1. Modify [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 [format.formatter.spec] as indicated:

    -6- An enabled specialization formatter<T, charT> meets the BasicFormatter requirements ([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 [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 [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);
    
Date: 2022-11-12.00:00:00

[ 2022-11-12 Approved at November 2022 meeting in Kona. Status changed: Voting → WP. ]

Date: 2022-08-24.00:00:00

[ 2022-08-24 Approved unanimously in LWG telecon. ]

Date: 2022-01-15.00:00:00

[ 2022-01-30; Reflector poll ]

Set priority to 1 after reflector poll.

Date: 2021-11-20.17:06:40

In libc++ review, we've noticed that we don't understand the implications of [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!

History
Date User Action Args
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2022-11-17 00:42:33adminsetmessages: + msg13051
2022-11-17 00:42:33adminsetstatus: voting -> wp
2022-11-08 03:46:49adminsetstatus: ready -> voting
2022-08-24 18:56:41adminsetmessages: + msg12712
2022-08-24 18:56:41adminsetstatus: new -> ready
2022-01-30 17:05:36adminsetmessages: + msg12313
2021-11-13 20:52:28adminsetmessages: + msg12216
2021-11-11 00:00:00admincreate