The following code has a couple of regular function overloads, and - now - a template function intended as a catch-all if no overload is suitable.
It almost works as I want, except that using derived classes (which previously ended up in the regular overload) get handled by the function template instead.
#include <iostream>
class Base { };
class AnotherBase { };
class Derv : public Base{ };
class Derv2 : public Base { };
class DervDerv : public Derv { };
void f(const Base &b)
{
printf("b(Base)\n");
}
void f(const Derv &b)
{
printf("b(Derv)\n");
}
template<class T> void f(const T& t)
{
printf("b(template)\n");
}
int main() {
f(Base());
f(AnotherBase());
f(Derv());
f(Derv2());
f(DervDerv());
return 0;
}
So the output I get is this...
b(Base)
b(template)
b(Derv)
b(template)
b(template)
... when what I'd naively expected was this:
b(Base)
b(template)
b(Derv)
b(Base)
b(Derv)
Is a function overload of a base class really ranked as "lower quality" than a function template? If so, is there an easy way to change this?
It's not about "quality". It's about conversions, just like it is with any other overload. To do overload resolution on the call to f(Derv2());
, a compiler will synthesize a declaration like this one from your function template:
void f(const Derv2& t);
Which it pits against the other declared overloads. This overload gets picked for the exact same reason f(const Derv &)
is a better match than f(const Base &)
when you write f(Derv());
.
A "catch-all" template will do just that, it will catch everything for which there isn't an exact user-defined overload. If you want to prevent that, you need to constrain the template with meta programming. A simple trick that relies on SFINAE would look like this:
template<class T>
auto f(const T& t) -> std::enable_if_t<!std::is_convertible<T*, Base*>::value>
{
printf("b(template)\n");
}
That produces the exact output you expected. Though obviously, you'd need to know what to constrain against in advance.