1. Introduction
This paper fixes a number of issues in range and tuple formatting related to
handling of empty specifiers for element types and clarifies that empty and not
present format specifiers are handled equivalently.
Originally it also amended the proposed resolution of [LWG3776] to allow
omitting calls to 
2. Changes from R1
- 
     Removed the permission to omit calls to parse 
- 
     Added calls to parse 
- 
     Clarified that format-spec cannot start with } 
- 
     Improved wording of set_debug_format 
3. Changes from R0
- 
     Added a comparison with potential alternative resolutions of the nested range/tuple formatting bug. 
4. Proposal
[LWG3776] "Avoid parsing format-spec if it is not present or empty" proposed
omitting the call to 
Consider the following example:
struct S {}; template <> struct std :: formatter < S > { constexpr auto parse ( format_parse_context & ctx ) { return ctx . begin (); } auto format ( S , format_context & ctx ) const { return ctx . out (); } }; int main () { auto s1 = std :: format ( "{}" , S ()); // (1) no format-spec auto s2 = std :: 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 
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 
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 
Barry Revzin pointed out an existing limitation of the formatting ranges design
that requires calling 
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 
Some potential alternative resolutions for the nested range/tuple formatting bug are:
- 
     Make range-based formatters conditionally debug-enabled specializations (S1). 
- 
     Always call the underlying formatter parse 
- 
     Default the output type of debug-enabled specializations in the constructor (S3). 
The table below compares alternative solutions with the earlier version (R1) of the current proposal denoted as S0:
|  Type  |  Format  |  Before  |  S1 & S2  |  S3  |  S0  | 
|---|---|---|---|---|---|
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
|  |  |  |  |  |  | 
S1 and S2 are inconsistent with the resolution of [LWG3776] earlier approved by LEWG and were not originally proposed. However, after LEWG reversed its two earlier decisions we are effectively stuck with S2 and the other options are only included for information.
S3 is similar to S0 and the difference is that in S3
the default of the element type is changed to the debug format. This means that
users have to give explicit specifiers to get the default format, e.g. 
auto v = std :: vector < char > { 'a' }; auto s1 = std :: format ( "{::}" , v ); // ['a'] in S3, [a] in S0 auto s2 = std :: format ( "{::c}" , v ); // [a] in both S0 and S3 
On the other hand combining the debug format with other specifiers such as width is easier in S3:
auto v = std :: vector < char > { 'a' }; auto s1 = std :: format ( "{::4}" , v ); // ['a' ] in S3, [a ] in S0 auto s2 = std :: format ( "{::4?}" , v ); // ['a' ] in both S0 and S3 
5. 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
6. Wording
This wording is relative to [N4917].
Modify [format.string.general] as indicated:
-1- ...
format-specifier:
   
    format-spec:
   as specified by the 
Modify 22.14.6.1 [formatter.requirements] as indicated:
-3- Given character type 
...
    
    -2- Let true and the default otherwise.
     to be as if the type of the std-format-spec parsed by the last call to 
     Each header that declares the
template 
...
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) template < class ParseContext > constexpr typename ParseContext :: iterator parse ( ParseContext & ctx ); 
    Effects: Parses the format 
    specifier
    specifiers
     as a range-format-spec and stores the parsed specifiers in If:
   
- 
     the range-type is neithers ? s 
- 
     underlying_ . set_debug_format () 
- 
     there is no range-underlying-spec,
underlying_ underlying_ . set_debug_format () 
    If there is a range-underlying-spec, then calls 
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) 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 ; return underlying_ . parse ( ctx ); 
In [format.tuple]:
-1- For each of 
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 underlying_ e . set_debug_format ( true) e . set_debug_format ( true) template < class ParseContext > constexpr typename ParseContext :: iterator parse ( ParseContext & ctx ); 
    -7- Effects: Parses the format 
    specifier
    specifiers
     as a tuple-format-spec 
    and
    ,
     stores the parsed specifiers in For each
element 
    For each
element 
-8- Returns: An iterator past the end of the tuple-format-spec.
Throws:format_error ctx . begin ()  !=  ctx . end () * ctx . begin ()  !=  '}' e . parse ( ctx ) e underlying_ 7. Acknowledgements
Thanks to Barry Revzin and Mark de Wever for pointing out issues with debug formatting of ranges and tuples.