A qualification-conversion (here: on the implicit object parameter) is not an identity conversion, and has a cost in overload resolution ranking
The member function void foo(B<T>& x) const is const-qualified, whereas the template member function template<class F> void foo(F f) is not. This means the latter is a better match for a call where the implicit object parameter is not const, as per [over.ics.rank]/3.2.5:
Standard conversion sequence S1 is a better conversion sequence than
standard conversion sequence S2 if
- [...], or, if not that, S1 and S2 differ only in their qualification conversion ([conv.qual]) and yield similar types T1 and T2,
respectively, where T1 can be converted to T2 by a qualification
conversion.
and [over.match.best]/2.1
If you const-qualify the automatic variable b in main, the non-template overload will be chosen:
// ^^^ `foo` overloads as in OP's example
B<int> a{};
B<int> const b{}
b.foo(a); // foo_B
If you instead const-quality the template member function foo, then the non-template will be the same match as the template overload (const-qualification needed on implicit object parameter), in which case the non-template function is chosen as the best viable overload, as per [over.match.best]/2.4.
If you never want a particular overload to participate in overload resolution for type predicate: remove it
However, b cannot be declared const in the actual application, which boil down to making a copy, say const c(b); and then use c.foo(a)
You could use a trait to remove the template member function when the template argument for its type template parameter is a specialization of the B class template:
#include <iostream>
#include <type_traits>
template <class T, template <class...> class Primary>
struct is_specialization_of : std::false_type {};
template <template <class...> class Primary, class... Args>
struct is_specialization_of<Primary<Args...>, Primary> : std::true_type {};
template <class T, template <class...> class Primary>
inline constexpr bool is_specialization_of_v{is_specialization_of<T, Primary>::value};
template <class T> class B {
public:
void foo(B<T> &x) const { std::cout << "foo_B" << std::endl; }
template <class F, typename = std::enable_if_t<!is_specialization_of_v<F, B>>>
void foo(F f) {
std::cout << "foo_F" << std::endl;
}
};
int main() {
B<int> a;
B<int> b;
b.foo(a);
b.foo([]() { ; });
return (0);
}
where we've leveraged the is_specialization_of trait of P2098R1 (just note that this has implementation variances for template arguments that are alias templates - somewhat underspecified IIRC).
Note that with this approach none of the overloads would be viable for an argument that is another specialization of B (than that of the implicit object parameter).