I did manage to find a solution based on the formattable function from the question and the concept from this StackOverflow answer:
template<typename T>
consteval bool formattable()
{
auto fmt_parse_ctx = std::format_parse_context(".2");
return std::formatter<T>().parse(fmt_parse_ctx) == fmt_parse_ctx.end();
}
template<typename T> concept Formattable = requires
{
typename std::type_identity_t<int[(formattable<T>(),1)]>;
};
static_assert(Formattable<double>);
static_assert(!Formattable<int>);