Skip to content

Formatter for std::reference_wrapper<T> results in an ambiguous template when T can be used with format_as #4424

@jeremy-rifkin

Description

@jeremy-rifkin

Fmt 11.1.0 added a formatter specialization for std::reference_wrapper<T>.

fmt/include/fmt/std.h

Lines 698 to 707 in c792524

template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char,
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>
: formatter<remove_cvref_t<T>, Char> {
template <typename FormatContext>
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<remove_cvref_t<T>, Char>::format(ref.get(), ctx);
}
};

This can result in errors due to ambiguous templates when T can be used with format_as:

#include <fmt/format.h>
#include <fmt/std.h>

#include <functional>

struct S {
    int x;
};

int format_as(S);

int main() {
    S s;
    fmt::format("{}", std::ref(s));
}
<source>:14:16:   in 'constexpr' expansion of 'fmt::v11::fstring<std::reference_wrapper<S> >("{}")'
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/base.h:1119:52: error: ambiguous template instantiation for 'struct fmt::v11::formatter<std::reference_wrapper<S>, char, void>'
 1119 |     remove_cvref_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>;
      |                             ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/format.h:3810:8: note: candidates are: 'template<class T, class Char> struct fmt::v11::formatter<T, Char, fmt::v11::void_t<typename std::remove_cv<typename std::remove_reference<decltype (format_as(declval<const T&>()))>::type>::type> > [with T = std::reference_wrapper<S>; Char = char]'
 3810 | struct formatter<T, Char, void_t<detail::format_as_result<T>>>
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from <source>:2:
/opt/compiler-explorer/libs/fmt/trunk/include/fmt/std.h:699:8: note:                 'template<class T, class Char> struct fmt::v11::formatter<std::reference_wrapper<_Tp>, Char, typename std::enable_if<std::integral_constant<bool, (! std::is_same<decltype (fmt::v11::detail::type_mapper<Char>::map(declval<typename std::conditional<std::is_void<typename std::remove_cv<typename std::remove_reference<_Tp>::type>::type>::value, int*, typename std::remove_cv<typename std::remove_reference<_Tp>::type>::type>::type&>())), void>::value)>::value, void>::type> [with T = S; Char = char]'
  699 | struct formatter<std::reference_wrapper<T>, Char,
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  700 |                  enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>
      |                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

https://godbolt.org/z/r71vbP4KG

This is an unfortunate consequence of std::reference_wrapper having a non-explicit operator T& which allows a call like format_as(std::ref(s)) to be valid.

Repro without fmt/std.h, manually providing the specialization: https://godbolt.org/z/TeeP49x95.

I can think of two possible solutions, I'm sure more exist:

One possible solution is to embrace format_as instead of specializing fmt::formatter for this:

template <typename T>
T& format_as(std::reference_wrapper<T>);

This solves the problem as naturally int format_as(S); is a better match https://godbolt.org/z/h173rEa6q

Another possible solution is to constrain fmt::formatter<std::reference_wrapper<T>> to types T which are not format_as-ible https://godbolt.org/z/j66Ko5Tzq.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions