Search code examples
c++moveforwarding-reference

move and templated constructor, wrong one is taken


I need to have a class which takes copy/move and a templated constructor like the following example:

    class A
{
    public:
    A() {}
    A( A&& ){std::cout << "Move" << std::endl;}
    A( const A& ){ std::cout << "copy" << std::endl;}

    template < typename T>
        A( T&& ) { std::cout << "any" << std::endl; }
};

class B: public A
{
    public:
    B():A() {}
    B( const B& b): A(b) {}
    B( B&& b): A( std::move(b)) {}
};

int main()
{
    B b;
    B b2( b );              // expected call copy
    B b3( std::move( b ));  // expected call to move

}

But I run into the templated constructor which is absolutely misterious to me. Why is the move constructor not the better match?

Found some hints here: Template Constructor Taking Precedence Over Normal Copy and Move Constructor?

In one of the comments I see that SFINAE is taken a possibility. But I could not see any valid implementation to select the correct constructor. Can someone give me a hint to pick up the correct constructor?

I expect to go up the inheritance hierarchy with the move constructors. So how to implement the move constructors that the correct one of the base class will be chosen. As this I expect the output "move" here instead of "any".

EDIT: In addition to the given answer the following solution seem also to work:

class B: public A
{    
    public:
        B():A() {}
        B( const B& b ): A( (const A&)b ) {}
        D( B&& b): A( std::forward<A>(b)) {}
};

That also explains why the template in the base class "eats" the constructor call, as the implicit cast from B to A is not needed for the template and so the template is the better match. If explicit casted before, all works fine.


Solution

  • In

    A( std::move(b))
    

    std::move(b) gives you a B&&. When overload resolution is performed it finds A( A&& ) and (from the template) A( B&& ). The template is an exact match, so it will chose that instead.

    To fix this, just get rid of the template, you don't need it. If you do need it in your actual code, then you can use SFINAE on the template constructor to stop it from being called by derived classes using

    template < typename T, std::enable_if_t<!std::is_convertible_v<T*, A*>, bool> = true>
    A( T&& ) { std::cout << "any" << std::endl; }