Skip to content

Commit fed5bab

Browse files
committed
New issue from Hewill: "The formattable type is not a formattable type"
1 parent 7e12653 commit fed5bab

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed

xml/issue4240.xml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
<?xml version='1.0' encoding='utf-8' standalone='no'?>
2+
<!DOCTYPE issue SYSTEM "lwg-issue.dtd">
3+
4+
<issue num="4240" status="New">
5+
<title>The formattable type is not a <code>formattable</code> type</title>
6+
<section>
7+
<sref ref="[format.formattable]"/>
8+
</section>
9+
<submitter>Hewill Kang</submitter>
10+
<date>06 Apr 2025</date>
11+
<priority>99</priority>
12+
13+
<discussion>
14+
<p>
15+
User-specific <code>formatter</code>s usually have the following form:
16+
</p>
17+
<blockquote><pre>
18+
template &lt;&gt; struct std::formatter&lt;T&gt; {
19+
constexpr auto parse(format_parse_context&amp; ctx)
20+
-> format_parse_context::iterator;
21+
22+
auto format(const T&amp; value, format_context&amp; ctx) const
23+
-> format_context::iterator;
24+
};
25+
</pre></blockquote>
26+
<p>
27+
This is reflected in wording examples such as <sref ref="[format.formatter.spec]"/> bullet 8 or
28+
<sref ref="[format.context]"/> bullet 9:
29+
</p>
30+
<blockquote><pre>
31+
#include &lt;format&gt;
32+
#include &lt;string&gt;
33+
34+
enum color { red, green, blue };
35+
const char* color_names[] = { "red", "green", "blue" };
36+
37+
template&lt;&gt; struct std::formatter&lt;color&gt; : std::formatter&lt;const char*&gt; {
38+
auto format(color c, format_context&amp; ctx) const {
39+
return formatter&lt;const char*&gt;::format(color_names[c], ctx);
40+
}
41+
};
42+
</pre></blockquote>
43+
<p>
44+
which allows us to format <code>color</code> with <code>std::format("{}", red)</code>.
45+
Unfortunately, even so, the <code>color</code> still does not satisfy <code>std::formattable</code>.
46+
<p/>
47+
This is because the concept <code>formattable</code> is currently defined as follows:
48+
</p>
49+
<blockquote><pre>
50+
template&lt;class T, class Context,
51+
class Formatter = typename Context::template formatter_type&lt;remove_const_t&lt;T&gt;&gt;&gt;
52+
concept <i>formattable-with</i> = // <i>exposition only</i>
53+
semiregular&lt;Formatter&gt; &amp;&amp;
54+
requires(Formatter&amp; f, const Formatter&amp; cf, T&amp;&amp; t, Context fc,
55+
basic_format_parse_context&lt;typename Context::char_type&gt; pc)
56+
{
57+
{ f.parse(pc) } -> same_as&lt;typename decltype(pc)::iterator&gt;;
58+
{ cf.format(t, fc) } -> same_as&lt;typename Context::iterator&gt;;
59+
};
60+
61+
template&lt;class T, class charT&gt;
62+
concept formattable =
63+
<i>formattable-with</i>&lt;remove_reference_t&lt;T&gt;, basic_format_context&lt;<i>fmt-iter-for</i>&lt;charT&gt;, charT&gt;&gt;;
64+
</pre></blockquote>
65+
<p>
66+
where <code><i>fmt-iter-for</i>&lt;charT&gt;</code> is an unspecified type that can write
67+
<code>charT</code>, which for <code>char</code> is <code>back_insert_iterator&lt;string&gt;</code>
68+
and <code>char*</code> in libstdc++ and libc++, respectively.
69+
</p>
70+
<p>
71+
That is, for <code>color</code> to satisfy <code>formattable</code>, it is
72+
necessary to ensure that <code>cf.format(t, fc)</code> is well-formed.
73+
</p>
74+
<p>
75+
However, the <code>format()</code> function in the above example takes a <code>format_context</code>
76+
whose <code>Out</code> parameter is internal iterator type, namely
77+
<code>__format::_Sink_iter&lt;char&gt;</code> and
78+
<code>back_insert_iterator&lt;__format::__output_buffer&lt;char&gt;&gt;</code> in
79+
libstdc++ and libc++, respectively.
80+
Since <code>basic_format_context</code> with different <code>Out</code> parameters cannot be converted to
81+
each other, the constraint is not satisfied.
82+
</p>
83+
<p>
84+
The reason <code>color</code> can still be formatted is that <code>basic_format_arg</code>
85+
checks for <code><i>formattable-with</i>&lt;Context&gt;</code> where <code>Context</code>
86+
has been correctly specified as <code>format_context</code>.
87+
</p>
88+
<p>
89+
And since <code>color</code> is formattable but not <code>formattable</code>, this further
90+
prevents formatting a range with elements of <code>color</code>, because the <code>formatter</code>
91+
specialization for ranges requires that the element type must be <code>formattable</code>.
92+
This leads to some inconsistencies (<a href="https://godbolt.org/z/Y8a6WTrK1">demo</a>):
93+
</p>
94+
<blockquote><pre>
95+
std::println("{}", red); // ok
96+
static_assert(std::formattable&lt;color, char&gt;); // <span style="color:#C80000;font-weight:bold">fires</span>
97+
98+
std::vector&lt;color&gt; v;
99+
std::println("{}", v); // <span style="color:#C80000;font-weight:bold">not ok</span>
100+
</pre></blockquote>
101+
<p>
102+
The workaround is to turn the custom <code>format()</code> into a template function
103+
such as <code>format(color c, auto&amp; ctx)</code> or
104+
<code>format(color c, basic_format_context&lt;Out, charT&gt;&amp; ctx)</code>,
105+
However, this seems mandate users to always declare <code>format()</code> as the template
106+
function for the best practice, which in my opinion defeats the purpose of introducing
107+
<code>format_context</code> in the first place.
108+
</p>
109+
<p>
110+
Also, since <code><i>fmt-iter-for</i>&lt;charT&gt;</code> is unspecified, if it is specified
111+
in some library implementation as the same type as <code>format_context</code>'s <code>Out</code>
112+
parameters, then <code>color</code> will suddenly become <code>formattable</code>. This lack
113+
of guarantee about <code>formattable</code> can bring unnecessary confusion.
114+
</p>
115+
<p>
116+
I think we should ensure that <code>color</code> is <code>formattable</code>, because it is formattable.
117+
</p>
118+
</discussion>
119+
120+
<resolution>
121+
<p>
122+
This wording is relative to <paper num="N5008"/>.
123+
</p>
124+
125+
<ol>
126+
127+
<li><p>Modify <sref ref="[format.formattable]"/> as indicated:</p>
128+
129+
<blockquote>
130+
<p>
131+
-1- <del>Let <code><i>fmt-iter-for</i>&lt;charT&gt;</code> be an unspecified type that models
132+
<code>output_iterator&lt;const charT&amp;&gt;</code> (<sref ref="[iterator.concept.output]"/>)</del>.
133+
</p>
134+
<blockquote><pre>
135+
[&hellip;]
136+
template&lt;class T, class charT&gt;
137+
concept formattable =
138+
<i>formattable-with</i>&lt;remove_reference_t&lt;T&gt;, <ins>conditional_t&lt;same_as&lt;charT, char&gt;, format_context, wformat_context&gt;</ins><del>basic_format_context&lt;<i>fmt-iter-for</i>&lt;charT&gt;, charT&gt;</del>&gt;;
139+
</pre></blockquote>
140+
</blockquote>
141+
142+
</li>
143+
144+
</ol>
145+
</resolution>
146+
147+
</issue>

0 commit comments

Comments
 (0)