This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
formattable
typeSection: 28.5.6.3 [format.formattable] Status: New Submitter: Hewill Kang Opened: 2025-04-06 Last modified: 2025-04-06
Priority: Not Prioritized
View other active issues in [format.formattable].
View all other issues in [format.formattable].
View all issues with New status.
Discussion:
User-specific formatter
s usually have the following form:
template <> struct std::formatter<T> { constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator; auto format(const T& value, format_context& ctx) const -> format_context::iterator; };
This is reflected in wording examples such as 28.5.6.4 [format.formatter.spec] bullet 8 or 28.5.6.7 [format.context] bullet 9:
#include <format> #include <string> 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); } };
which allows us to format color
with std::format("{}", red)
.
Unfortunately, even so, the color
still does not satisfy std::formattable
.
formattable
is currently defined as follows:
template<class T, class Context, class Formatter = typename Context::template formatter_type<remove_const_t<T>>> concept formattable-with = // exposition only semiregular<Formatter> && requires(Formatter& f, const Formatter& cf, T&& t, Context fc, basic_format_parse_context<typename Context::char_type> pc) { { f.parse(pc) } -> same_as<typename decltype(pc)::iterator>; { cf.format(t, fc) } -> same_as<typename Context::iterator>; }; template<class T, class charT> concept formattable = formattable-with<remove_reference_t<T>, basic_format_context<fmt-iter-for<charT>, charT>>;
where fmt-iter-for<charT>
is an unspecified type that can write
charT
, which for char
is back_insert_iterator<string>
and char*
in libstdc++ and libc++, respectively.
That is, for color
to satisfy formattable
, it is
necessary to ensure that cf.format(t, fc)
is well-formed.
However, the format()
function in the above example takes a format_context
whose Out
parameter is internal iterator type, namely
__format::_Sink_iter<char>
and
back_insert_iterator<__format::__output_buffer<char>>
in
libstdc++ and libc++, respectively.
Since basic_format_context
with different Out
parameters cannot be converted to
each other, the constraint is not satisfied.
The reason color
can still be formatted is that basic_format_arg
checks for formattable-with<Context>
where Context
has been correctly specified as format_context
.
And since color
is formattable but not formattable
, this further
prevents formatting a range with elements of color
, because the formatter
specialization for ranges requires that the element type must be formattable
.
This leads to some inconsistencies (demo):
std::println("{}", red); // ok static_assert(std::formattable<color, char>); // fires std::vector<color> v; std::println("{}", v); // not ok
The workaround is to turn the custom format()
into a template function
such as format(color c, auto& ctx)
or
format(color c, basic_format_context<Out, charT>& ctx)
,
However, this seems mandate users to always declare format()
as the template
function for the best practice, which in my opinion defeats the purpose of introducing
format_context
in the first place.
Also, since fmt-iter-for<charT>
is unspecified, if it is specified
in some library implementation as the same type as format_context
's Out
parameters, then color
will suddenly become formattable
. This lack
of guarantee about formattable
can bring unnecessary confusion.
I think we should ensure that color
is formattable
, because it is formattable.
Proposed resolution:
This wording is relative to N5008.
Modify 28.5.6.3 [format.formattable] as indicated:
-1-
Let.fmt-iter-for<charT>
be an unspecified type that modelsoutput_iterator<const charT&>
(24.3.4.10 [iterator.concept.output])[…] template<class T, class charT> concept formattable = formattable-with<remove_reference_t<T>, conditional_t<same_as<charT, char>, format_context, wformat_context>basic_format_context<fmt-iter-for<charT>, charT>>;