4
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?

spraff
  • 32,570
  • 22
  • 121
  • 229

2 Answers2

5

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 &);
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • Maybe play also with `const`, as `const Parent parent; Foo{parent};` matches no constructors (OP uses `Foo (Parent &)` and `Foo (const T&)`...). – Jarod42 Nov 07 '18 at 11:00
0

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.

Oliv
  • 17,610
  • 1
  • 29
  • 72