Move has_format_as to format.h and use it in the tuple formatter

The tuple formatter had a bespoke trait to keep format_as from producing an
ambiguous specialization. detail::has_format_as already answers that question
and std.h applies it the same way, so move it from std.h to format.h where
ranges.h can see it and drop the extra trait.

Check it with conditional_t rather than &&: is_tuple_formattable must not be
instantiated for types with format_as because for self-referential tuple-like
types that recurses back into formatter selection, and && short-circuits
evaluation but not instantiation.
This commit is contained in:
Victor Zverovich 2026-09-20 09:49:07 -07:00
parent cfb57bfa3a
commit 79e48ba915
3 changed files with 15 additions and 20 deletions

View File

@ -4100,6 +4100,11 @@ template <typename Char> class nested_format_specs {
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}
};
template <typename T, typename Enable = void>
struct has_format_as : std::false_type {};
template <typename T>
struct has_format_as<T, void_t<format_as_result<T>>> : std::true_type {};
} // namespace detail
FMT_BEGIN_EXPORT

View File

@ -297,21 +297,17 @@ template <typename T, typename C> struct is_tuple_formattable {
static constexpr bool value = detail::is_tuple_formattable_<T, C>::value;
};
namespace detail {
template <typename T, typename C, typename = void>
struct is_tuple_formattable_for_formatter
: std::integral_constant<bool, fmt::is_tuple_formattable<T, C>::value> {};
template <typename T, typename C>
struct is_tuple_formattable_for_formatter<T, C, void_t<format_as_result<T>>>
: std::false_type {};
} // namespace detail
template <typename Tuple, typename Char>
struct formatter<Tuple, Char,
enable_if_t<fmt::is_tuple_like<Tuple>::value &&
detail::is_tuple_formattable_for_formatter<
Tuple, Char>::value>> {
struct formatter<
Tuple, Char,
// format_as takes precedence. conditional_t rather than && because
// is_tuple_formattable must not be instantiated for types with format_as:
// for self-referential tuple-like types it recurses back into this
// specialization.
enable_if_t<
fmt::is_tuple_like<Tuple>::value &&
conditional_t<detail::has_format_as<Tuple>::value, std::false_type,
fmt::is_tuple_formattable<Tuple, Char>>::value>> {
private:
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;

View File

@ -279,12 +279,6 @@ struct is_bit_reference_like<std::__bit_const_reference<C>> {
};
#endif
template <typename T, typename Enable = void>
struct has_format_as : std::false_type {};
template <typename T>
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
: std::true_type {};
template <typename T, typename Enable = void>
struct has_format_as_member : std::false_type {};
template <typename T>