ARTICLE AD BOX
I don't know why the overload selection is made as such, but thanks to @NathanOliver I found an explanation and a workaround:
The usage of the member function doesn't matter here.
The inline overload is always selected because it is preferred against the const & one, and then a compile error occurs because there is no suitable append() overload.
To force the use of the correct operator I added this version of append() to the Builder class:
template<typename T, typename = std::enable_if_t<std::is_class_v<T>>> Packet & append(T const & t) { return *this << t; }10.7k4 gold badges33 silver badges41 bronze badges
To see "why" keep in mind that an auto parameter type is basically shorthand for a template, so your code is roughly equivalent to this:
Builder & append(int); Builder & append(double); template <class T> Builder & operator<<(T && t) { return append(t); } // [1] };Since it calls append, T has to be either int, double, or some type that will implicitly convert to int or double. Since there's no conversion from X to int or double, it fails. We can make it work by adding a conversion from X to int:
struct X { friend Builder & operator<<(Builder & p, X const &) { return p << 42; } operator int() { return 1; } // conversion to `double` would work just as well. };...or by adding an overload of append that takes an X:
struct X; struct Builder { Builder & append(int); Builder & append(double); Builder & append(X const &); // ... many other overloads Builder & operator<<(auto && t) { return append(t); } // [1] // ...Either will work. In this situation, you can have both, but if you ended up with equal conversion sequences, you'd get an ambiguity and it wouldn't compile.
495k83 gold badges658 silver badges1.2k bronze badges
Explore related questions
See similar questions with these tags.
