How can I make C++ prefer to match a parent class overload instead of a template?

314 Views Asked by At
class Parent {};

class Child : public Parent {};

class Foo
{
public:

    Foo (Parent &) {};

    template <typename T>
    Foo (const T &);
};

int main ()
{
    Child c;

    Foo foo (c);
}

This produces a linker error since the constructor for foo chooses template<typename T>Foo::Foo(const T &) instead of Foo::Foo(Parent&).

If c has type Parent instead of Child, this uses the non-template constructor and links with no issues.

I can work around this with

Foo foo ((Parent&) c);

but I don't want to do that.

Why does C++ prefer to use a template instead of implicitly casting c to Parent&?

Can I change the class to prefer casting to templating, so the workaround is not needed?

2

There are 2 best solutions below

1
On

The compiler prefers to choose the template constructor with T=child because overload resolution considers that a qualification conversion (adding const to the argument type) is better than a derived to base conversion.

So the simplest way is just to declare a constructor taking a child as argument:

class Foo
{
public:

    Foo (Parent &) {};

    Foo (Child & x):Foo(static_cast<Parent&>(x)) {};

    template <typename T>
    Foo (const T &);
};

Notice that if the argument to the constructor is a const lvalue or an rvalue, then as in your example code the template constructor will be selected. I suppose that this is intentional.

1
On

One solution is to disable the template constructor through SFINAE:

template <
    typename T,
    std::enable_if_t<!std::is_base_of_v<Parent, T>> * = 0
>
Foo (const T &);