1. Proposal
[LWG3776] "Avoid parsing format-spec if it is not present or empty" proposed
omitting the call to for empty format specifiers
(format-spec in [format.string.general] of [N4917]).
Consider the following example:
struct S {}; template <> struct std :: formatter < S > { auto parse ( format_parse_context & ctx ) { return ctx . begin (); } auto format ( S , format_context & ctx ) const { return ctx . out (); } }; int main () { auto s1 = fmt :: format ( "{}" , S ()); // (1) no format-spec auto s2 = fmt :: format ( "{:}" , S ()); // (2) empty format-spec }
In (1) format-spec is not present and in (2) it is present but empty.
There is nothing to parse in both of these cases and therefore requiring
implementations to call doesn’t make a lot of sense.
It only adds unnecessary overhead for the common case which is what [LWG3776] was proposing to eliminate. Implementation experience in {fmt} showed that
requiring the call to has negative impact on formatting of ranges where
we had to unnecessarily call this function from multiple places. The same issue
may exist in other contexts such as format string compilation.
Additionally [LWG3776] made a drive-by fix, clarifying that the two cases are
equivalent which was not obvious from existing wording. This is arguably even
more important than omitting , particularly because formatting of ranges
([P2286]) doesn’t allow distinguishing between the two forms for nested
specifiers, e.g.
auto s = std :: format ( "{::}" , std :: vector < S > ( 2 )); // ^ empty format-spec for S
Having the two cases equivalent is also more intuitive and consistent with all existing standard formatters.
Library Evolution Working Group (LEWG) reviewed [LWG3776] in Kona and
approved it with the amendment that implementations are allowed but not required
to omit the call to for empty format-spec.
Barry Revzin pointed out an existing limitation of the formatting ranges design
that requires calling from the function. However,
as discovered by Mark de Wever while implementing ranges formatting in libc++,
the specialization for tuples already omits the call to for the underlying type so we need to fix this anyway. The following example
illustrates the fix:
auto s = fmt :: format ( "{}" , std :: make_tuple ( std :: make_tuple ( 'a' )));
| Before | After |
|---|---|
|
|
This paper amends the proposed resolution of [LWG3776] per LEWG feedback and
makes the necessary changes to the API both to enable the
proposed resolution and to fix tuple formatting. It also fixes a specification
bug in that doesn’t mention calling for the
underlying formatter. This proposal has been implemented in [FMT] and in a
branch of [LIBCXX].
2. LEWG Poll Results
POLL: Relax the requirements table 74 and 75 to make the optimization allowed by the issue resolution of LWG3776 a QoI issue with additional changes to the handle class removed
| SF | F | N | A | SA |
|---|---|---|---|---|
| 1 | 9 | 2 | 1 | 1 |
Outcome: consensus in favour
POLL: Adopt the amended proposed resolution of LWG3776 "Avoid parsing format-spec if it is not present or empty". Return the issue to LWG for C++23 (to be confirmed by electronic polling)
| SF | F | N | A | SA |
|---|---|---|---|---|
| 2 | 6 | 1 | 2 | 1 |
Outcome: weak consensus in favour
3. Wording
This wording is relative to [N4917].
Modify 22.14.6.1 [formatter.requirements] as indicated:
-3- Given character type , output iterator type , and formatting
argument type , in Table 74 and Table 75:
...
points to the beginning of the format-spec (22.14.2
[format.string]) of the replacement field being formatted in the format string.
If format-spec is
not present or
empty then either or .
Modify BasicFormatter requirements [tab:formatter.basic] as indicated:
| Expression | Return type | Requirement |
|---|---|---|
|
|
Formats according to the specifiers stored in , writes the output to , and returns
an iterator past the end of the output range.
The output shall only depend on , , for any value of type , and
the range from the last
call to .
When the format-spec (22.14.2 [format.string]) is not present or empty
the call to may be omitted.
|
Modify Formatter requirements [tab:formatter] as indicated:
| Expression | Return type | Requirement |
|---|---|---|
|
|
Formats according to the specifiers stored in , writes the output to , and returns
an iterator past the end of the output range.
The output shall only depend on , , for any value of type , and
the range from the last
call to .
When the format-spec (22.14.2 [format.string]) is not present or empty
the call to may be omitted.
|
-2- Let be either or . Each specialization of is either enabled or disabled, as described below. A debug-enabled specialization of additionally provides a public, constexpr,
non-static member function which modifies the state of the to be as if the type of the std-format-spec parsed by the last call to were
if is true and none otherwise
. Each header that declares the template provides the following enabled specializations:
...
namespace std { template < class T , class charT = char > requires same_as < remove_cvref_t < T > , T > && formattable < T , charT > class range_formatter { ... constexpr const formatter < T , charT >& underlying () const { return underlying_ ; } constexpr range_formatter (); template < class ParseContext > constexpr typename ParseContext :: iterator parse ( ParseContext & ctx ); }; }
...
Effects: Equivalent to:constexpr void set_brackets ( basic_string_view < charT > opening , basic_string_view < charT > closing );
opening - bracket_ = opening ; closing - bracket_ = closing ;
Effects: Callsconstexpr range_formatter ();
underlying_ . set_debug_format ( true) if it is a valid
expression.
template < class ParseContext > constexpr typename ParseContext :: iterator parse ( ParseContext & ctx );
Effects: Parses the format specifier
s
as a range-format-spec and stores the parsed specifiers in . The values of , , and are
modified if and only if required by the range-type or the option, if
present.
If:
-
the range-type is neithernors ,? s -
is a valid expression, andunderlying_ . set_debug_format () -
there is no range-underlying-spec,
underlying_ underlying_ . set_debug_format () .If there is range-underlying-spec:
-
calls
if it is a valid expression,underlying_ . set_debug_format ( false) -
calls
to parse range-underlying-spec.underlying_ . parse
In [format.range.fmtstr]:
namespace std { template < range_format K , ranges :: input_ range R , class charT > requires ( K == range_format :: string || K == range_format :: debug_string ) struct range - default - formatter < K , R , charT > { ... public : constexpr range - default - formatter (); template < class ParseContext > constexpr typename ParseContext :: iterator parse ( ParseContext & ctx ); ... }; }
Effects: Callsconstexpr range - default - formatter ();
underlying_ . set_debug_format ( true) if it is a valid
expression and K == range_format :: debug_string .
template < class ParseContext > constexpr typename ParseContext :: iterator parse ( ParseContext & ctx );
-2- Effects: Equivalent to:
auto i = underlying_ . parse ( ctx ); if constexpr ( K == range_format :: debug_string ) { underlying_ . set_debug_format ( true); } return i ; if constexpr ( K == range_format :: debug_string ) { underlying_ . set_debug_format ( false); } return underlying_ . parse ( ctx );
In [format.tuple]:
-1- For each of and , the library provides the following formatter
specialization where pair-or-tuple is the name of the template:
namespace std { template < class charT , formattable < charT > ... Ts > struct formatter < pair - or - tuple < Ts ... > , charT > { ... constexpr void set_brackets ( basic_string_view < charT > opening , basic_string_view < charT > closing ); constexpr formatter (); template < class ParseContext > constexpr typename ParseContext :: iterator parse ( ParseContext & ctx ); }; }
...
constexpr void set_brackets ( basic_string_view < charT > opening , basic_string_view < charT > closing );
-6- Effects: Equivalent to:
opening - bracket_ = opening ; closing - bracket_ = closing ;
Effects: For each elementconstexpr formatter ();
e in underlying_ , if e . set_debug_format ( true) is a valid expression, calls e . set_debug_format ( true) .
template < class ParseContext > constexpr typename ParseContext :: iterator parse ( ParseContext & ctx );
-7- Effects: Parses the format specifier
s
as a tuple-format-spec and stores the parsed specifiers in . The values of , , and are
modified if and only if required by the tuple-type, if present.
For each
element
in , if is a valid expression, calls .
-8- Returns: An iterator past the end of the tuple-format-spec.
4. Acknowledgements
Thanks to Barry Revzin and Mark de Wever for pointing out issues with debug formatting of ranges and tuples.