@@ -31,6 +31,7 @@ concept ConvertibleFrom = std::convertible_to<From, To>;
31
31
32
32
template <class T , class Char , class U >
33
33
concept StreamCanReceiveString =
34
+ // / Ensure that we don't accidentally recursively check `U`.
34
35
!std::same_as<std::remove_cvref_t <T>, std::remove_cvref_t <U>> &&
35
36
requires (T& t, const std::basic_string<Char> s) {
36
37
// Check ConvertibleFrom as std streams return std::basic_ostream&, which the
@@ -60,30 +61,19 @@ S& format_to_stream(S& os, const std::basic_string<Char>& s) {
60
61
// /
61
62
// / # Implementation Notes
62
63
// /
63
- // / The `Type` argument is encoded as a template argument for the GCC compiler
64
- // / because it does not reject the overload when the `Type` does not match
65
- // / otherwise, and then ends up recursively trying to solve
66
- // / `StreamCanReceiveString<S, char>`. On the first attempt to solve
67
- // / `StreamCanReceiveString<S, char>`, it tries to call this overload with
68
- // / `std::string` which is *not* `Type` and yet it considers it a valid
69
- // / overload, so it tries to again solve `StreamCanReceiveString<S, char>` which
70
- // / is now recursive. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99599.
71
- // /
72
- // / Actually it gets worse. If the `Type` has template parameters, we can't use
73
- // / `std::same_as<Type<Params..>> Sus_ValueType` as the compiler can't
74
- // / infer the `Params...` there, even though `Sus_ValueType` appears in the
75
- // / parameter list. So we _have_ to put the real type in the parameter list.
76
- // / But also in this case GCC does the same thing as the others and does not
77
- // / include the function in the overload set if the non-concept type doesn't
78
- // / match. So we can revert back to the usual incantation then.
79
- //
64
+ // / The `U` template type parameter allows `_sus_format_to_stream` to be a
65
+ // / hidden friend when `Type` is not a class template. Directly using `Type`
66
+ // / means that the function is immediately instantiated, and we'll get a
67
+ // / compile-time error unless `fmt::formatter` is specialised before `Type`'s
68
+ // / definition, since `fmt::is_formattable` doesn't know it's a formattable type
69
+ // / at this point in the code. This isn't a problem for primary class templates,
70
+ // / but it is problematic for regular classes and full specialisations. Using a
71
+ // / dependent type `U` defers instantiation until `operator<<` is first used.
72
+ // / We need to constrain `U` to be the same type as `Type`, otherwise it will
73
+ // / be ambiguous as to which overload we want.
80
74
// clang-format off
81
75
#define _sus_format_to_stream (Type ) \
82
76
template < \
83
- /* Inserts `std::same_as<Type> Sus_ValueType` if required for GCC. */ \
84
- sus_if_gcc ( \
85
- _sus_format_to_stream_parameter_concept (Type) \
86
- ) \
87
77
sus::string::__private::StreamCanReceiveString<char , Type> Sus_StreamType, \
88
78
std::same_as<Type> U = Type \
89
79
> \
0 commit comments