When implementing a formatter specialization for a user-defined type, I was surprised to see some exceptions thrown at runtime instead of at compile time. It seems that when a replacement field has an empty format specification (i.e. "{}"), then formatter<...>::parse() gets called at runtime, whereas if there is any format specification, even if it's empty (i.e. "{:}") then parse() gets called at compile time. It would certainly be preferable for parse() to be called at compile time in the empty format specification case. Here's an example:
#include <fmt/format.h>
struct WrappedInt { int val; };
// The formatter for WrappedInt unconditionally rejects its format specifications on purpose
// to make it clear when format strings are checked at compile time vs runtime
template <>
struct fmt::formatter<WrappedInt>
{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx)
{
throw fmt::format_error("Never happy");
return ctx.begin();
}
auto format(const WrappedInt& wrappedInt, format_context& ctx) const
{
return fmt::format_to(ctx.out(), "{}", wrappedInt.val);
}
};
int main()
{
// fmt::print("{:}", WrappedInt(1)); // When uncommented, this line gives a compiler error, which is ideal
fmt::print("{}", WrappedInt(1)); // This line however, leads to an exception being thrown at runtime
}
When implementing a formatter specialization for a user-defined type, I was surprised to see some exceptions thrown at runtime instead of at compile time. It seems that when a replacement field has an empty format specification (i.e. "{}"), then formatter<...>::parse() gets called at runtime, whereas if there is any format specification, even if it's empty (i.e. "{:}") then parse() gets called at compile time. It would certainly be preferable for parse() to be called at compile time in the empty format specification case. Here's an example: