<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Issue 4107: Map formatter may conflict with user-defined specializations of pair/tuple formatters</title>
<meta property="og:title" content="Issue 4107: Map formatter may conflict with user-defined specializations of pair/tuple formatters">
<meta property="og:description" content="C++ library issue. Status: New">
<meta property="og:url" content="https://cplusplus.github.io/LWG/issue4107.html">
<meta property="og:type" content="website">
<meta property="og:image" content="http://cplusplus.github.io/LWG/images/cpp_logo.png">
<meta property="og:image:alt" content="C++ logo">
<style>
  p {text-align:justify}
  li {text-align:justify}
  pre code.backtick::before { content: "`" }
  pre code.backtick::after { content: "`" }
  blockquote.note
  {
    background-color:#E0E0E0;
    padding-left: 15px;
    padding-right: 15px;
    padding-top: 1px;
    padding-bottom: 1px;
  }
  ins {background-color:#A0FFA0}
  del {background-color:#FFA0A0}
  table.issues-index { border: 1px solid; border-collapse: collapse; }
  table.issues-index th { text-align: center; padding: 4px; border: 1px solid; }
  table.issues-index td { padding: 4px; border: 1px solid; }
  table.issues-index td:nth-child(1) { text-align: right; }
  table.issues-index td:nth-child(2) { text-align: left; }
  table.issues-index td:nth-child(3) { text-align: left; }
  table.issues-index td:nth-child(4) { text-align: left; }
  table.issues-index td:nth-child(5) { text-align: center; }
  table.issues-index td:nth-child(6) { text-align: center; }
  table.issues-index td:nth-child(7) { text-align: left; }
  table.issues-index td:nth-child(5) span.no-pr { color: red; }
  @media (prefers-color-scheme: dark) {
     html {
        color: #ddd;
        background-color: black;
     }
     ins {
        background-color: #225522
     }
     del {
        background-color: #662222
     }
     a {
        color: #6af
     }
     a:visited {
        color: #6af
     }
     blockquote.note
     {
        background-color: rgba(255, 255, 255, .10)
     }
  }
</style>
</head>
<body>
<hr>
<p><em>This page is a snapshot from the LWG issues list, see the <a href="lwg-active.html">Library Active Issues List</a> for more information and the meaning of <a href="lwg-active.html#New">New</a> status.</em></p>
<h3 id="4107"><a href="lwg-active.html#4107">4107</a>. Map formatter may conflict with user-defined specializations of <code>pair</code>/<code>tuple</code> formatters</h3>
<p><b>Section:</b> 28.5.7.4 <a href="https://wg21.link/format.range.fmtmap">[format.range.fmtmap]</a> <b>Status:</b> <a href="lwg-active.html#New">New</a>
 <b>Submitter:</b> Victor Zverovich <b>Opened:</b> 2024-05-18 <b>Last modified:</b> 2025-02-07</p>
<p><b>Priority: </b>3
</p>
<p><b>View all issues with</b> <a href="lwg-status.html#New">New</a> status.</p>
<p><b>Discussion:</b></p>
<p>
Consider the following <a href="https://www.godbolt.org/z/e6Pr537EY">example</a>:
</p>
<blockquote><pre>
#include &lt;format&gt;
#include &lt;map&gt;
#include &lt;print&gt;

struct x {};

template&lt;typename K&gt;
struct std::formatter&lt;std::pair&lt;K, x&gt;&gt; : std::formatter&lt;std::string_view&gt; {
  auto format(const std::pair&lt;K, x&gt;&amp; p, auto&amp; ctx) const {
    return std::format_to(ctx.out(), "x/x");
  }
};

int main() {
  std::print("{}", std::map&lt;x, x&gt;());
}
</pre></blockquote>
<p>
It doesn't compile because the formatter for maps requires the element formatter to have 
<code>set_brackets</code> and <code>set_separator</code> (28.5.7.4 <a href="https://wg21.link/format.range.fmtmap">[format.range.fmtmap]</a>):
</p>
<blockquote><pre>
<i>underlying_</i>.underlying().set_brackets({}, {});
<i>underlying_</i>.underlying().set_separator(<i>STATICALLY-WIDEN</i>&lt;charT&gt;(": "));
</pre></blockquote>
<p>
The specialization <code>std::formatter&lt;std::pair&lt;K, x&gt;&gt;</code> itself is allowed according to 
16.4.5.2.1 <a href="https://wg21.link/namespace.std">[namespace.std]</a>:
</p>
<blockquote>
<p>
Unless explicitly prohibited, a program may add a template specialization for any standard library class 
template to namespace <code>std</code> provided that
</p>
<ul>
<li><p>the added declaration depends on at least one program-defined type, and</p></li>
<li><p>the specialization meets the standard library requirements for the original template.</p></li>
</ul>
</blockquote>
<p>
but it's unclear what exactly the part "the specialization meets the standard library requirements for 
the original template" means for this formatter. Does it mean that the specialization must provide 
<code>set_brackets</code> and <code>set_separator</code> and does the output have to be consistent with the main 
template? The latter would render the specialization useless. On the other hand if users are allowed to 
customize pair and tuple formatting the current specification of the map formatter is broken.
<p/>
The correct resolution appears to be to not restrict user-defined formatter specializations of <code>pair</code>s 
and <code>tuple</code>s, and make map be responsible for its own structural formatting rather than delegating 
part of it to other formatters in an arbitrary way. This resolution has been applied to {fmt}'s implementation 
of range formatting to address <a href="https://github.com/fmtlib/fmt/issues/3685">#3685</a>.
</p>

<p><i>[2025-02-07; Reflector poll]</i></p>

<p>
Set priority to 3 after reflector poll.
Several votes for NAD (a specialization must provide <code class='backtick'>set_brackets</code> etc.),
and suggestions it's a design change that LEWG should see.
</p>



<p id="res-4107"><b>Proposed resolution:</b></p>
<p>
This wording is relative to <a href="https://wg21.link/N4981" title=" Working Draft, Programming Languages — C++">N4981</a>.
</p>

<ol>

<li><p>Modify 28.5.8.3 <a href="https://wg21.link/format.args">[format.args]</a> as indicated:</p>

<blockquote>
<blockquote>
<pre>
namespace std {
  template&lt;ranges::input_range R, class charT&gt;
  struct <i>range-default-formatter</i>&lt;range_format::map, R, charT&gt; {
  private:
    using <i>maybe-const-map</i> = <i>fmt-maybe-const</i>&lt;R, charT&gt;;            // <i>exposition only</i>
    using <i>element-type</i> =                                          // <i>exposition only</i>
      remove_cvref_t&lt;ranges::range_reference_t&lt;<i>maybe-const-map</i>&gt;&gt;;
    <del>range_formatter&lt;<i>element-type</i>, charT&gt; <i>underlying_</i>;             // <i>exposition only</i></del>
    <ins>using <i>key-type</i> = tuple_element_t&lt;0, <i>element-type</i>&gt;;            // <i>exposition only</i>
    using <i>value-type</i> = tuple_element_t&lt;1, <i>element-type</i>&gt;;          // <i>exposition only</i>
    formatter&lt;<i>key-type</i>, charT&gt; <i>key-formatter_</i>;                    // <i>exposition only</i>
    formatter&lt;<i>value-type</i>, charT> <i>value-formatter_</i>;                // <i>exposition only</i></ins>

  public:
    constexpr <i>range-default-formatter</i>();
    
    template&lt;class ParseContext&gt;
      constexpr typename ParseContext::iterator
        parse(ParseContext&amp; ctx);
        
    template&lt;class FormatContext&gt;
      typename FormatContext::iterator
        format(<i>maybe-const-map</i>&amp; r, FormatContext&amp; ctx) const;
  };
}
</pre>
</blockquote>
<pre>
constexpr <i>range-default-formatter</i>();
</pre>
<blockquote>
<p>
-1- <i>Mandates</i>: Either:
</p>
<ol style="list-style-type:none">
<li><p>(1.1) &mdash; <code><i>element-type</i></code> is a specialization of <code>pair</code>, or</p></li>
<li><p>(1.2) &mdash; <code><i>element-type</i></code> is a specialization of <code>tuple</code> and 
<code>tuple_size_v&lt;<i>element-type</i>&gt; == 2</code>.</p></li>
</ol>
<p>
<del>-2- <i>Effects</i>: Equivalent to:</del>
</p>
<blockquote><pre>
<del><i>underlying_</i>.set_brackets(<i>STATICALLY-WIDEN</i>&lt;charT&gt;("{"), <i>STATICALLY-WIDEN</i>&lt;charT&gt;("}"));
<i>underlying_</i>.underlying().set_brackets({}, {});
<i>underlying_</i>.underlying().set_separator(<i>STATICALLY-WIDEN</i>&lt;charT&gt;(": "));</del>
</pre></blockquote>
</blockquote>
<pre>
template&lt;class ParseContext&gt;
  constexpr typename ParseContext::iterator
    parse(ParseContext&amp; ctx);
</pre>
<blockquote>
<p>
-3- <i>Effects</i>: <del>Equivalent to: <code>return <i>underlying_</i>.parse(ctx);</code></del>
<ins>Parses the format specifiers as a <i>range-format-spec</i> and stores the parsed specifiers in <code>*this</code>.</ins>
<p/>
<ins>If <code><i>key-formatter_</i>.set_debug_format()</code> is a valid expression, and there is no <i>range-underlying-spec</i>, 
then calls <code><i>key-formatter_</i>.set_debug_format()</code>.</ins>
<p/>
<ins>If <code><i>value-formatter_</i>.set_debug_format()</code> is a valid expression, and there is no <i>range-underlying-spec</i>, 
then calls <code><i>value-formatter_</i>.set_debug_format()</code>.</ins>
<p/>
<ins>-?- <i>Returns</i>: An iterator past the end of the <i>range-format-spec</i>.</ins>
</p>
</blockquote>
<pre>
template&lt;class FormatContext&gt;
  typename FormatContext::iterator
    format(<i>maybe-const-map</i>&amp; r, FormatContext&amp; ctx) const;
</pre>
<blockquote>
<p>
-4- <i>Effects</i>: <del>Equivalent to: <code>return <i>underlying_</i>.format(r, ctx);</code></del>
<ins>Writes the following into <code>ctx.out()</code>, adjusted according to the <i>range-format-spec</i>:</ins>
</p>
<ol style="list-style-type:none">
<li><p><ins>&mdash; <code><i>STATICALLY-WIDEN</i>&lt;charT&gt;("{")</code> unless the <code>n</code> option is specified,</ins></p></li>
<li><p><ins>&mdash; for each element <code>e</code> of the range <code>r</code>:</ins></p>
<ol style="list-style-type:none">
<li><p><ins>&mdash; the result of writing <code>get&lt;0&gt;(e)</code> via <code><i>key-formatter_</i></code>,</ins></p></li>
<li><p><ins>&mdash; <code><i>STATICALLY-WIDEN</i>&lt;charT&gt;(": ")</code>,</ins></p></li>
<li><p><ins>&mdash; the result of writing <code>get&lt;1&gt;(e)</code> via <code><i>value-formatter_</i></code>,</ins></p></li>
<li><p><ins>&mdash; <code><i>STATICALLY-WIDEN</i>&lt;charT&gt;(", ")</code>, unless <code>e</code> is the last element of <code>r</code>, and</ins></p></li>
</ol>
</li>
<li><p><ins>&mdash; <code><i>STATICALLY-WIDEN</i>&lt;charT&gt;("}")</code> unless the <code>n</code> option is specified.</ins></p></li>
</ol>
<p>
<ins>-?- <i>Returns</i>: An iterator past the end of the output range.</ins>
</p>
</blockquote>
</blockquote>

</li>
</ol>





</body>
</html>
