formattable typeSection: 28.5.6.3 [format.formattable] Status: New Submitter: Hewill Kang Opened: 2025-04-06 Last modified: 2025-06-12
Priority: 2
View other active issues in [format.formattable].
View all other issues in [format.formattable].
View all issues with New status.
Discussion:
User-specific formatters 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.
[2025-06-12; Reflector poll]
Set priority to 2 after reflector poll.
"This change would prevent future evolution of the API."
"Maybe formatter is not useful and formattable_with should always be used."
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>>;