Why isn't this templated move constructor being called?
Asked Answered
C

1

0

I've run into this weird situation:

template <typename pointed_t>
class MyPointer
{public:
    MyPointer() : pPointed(nullptr) {}
    /* PREVENT COMPILER-GENERATED FUNCTIONS */
    MyPointer(const MyPointer&);
    MyPointer(MyPointer&&);
    MyPointer& operator=(const MyPointer&);
    MyPointer& operator=(MyPointer&&);

    //----------
    pointed_t* pPointed;


    /* COPY CONSTRUCTOR */
    template <typename AnyPointerType>
    MyPointer(AnyPointerType& other)
    {
        
    }
    /* MOVE CONSTRUCTOR */
    template <typename AnyPointerType>
    MyPointer(AnyPointerType&& other)
    {
        

    }

};

int main()
{
    MyPointer<char> p1;

    MyPointer<char> p2 = p1; // COPY CONSTRUCTOR CALLED FINE
    MyPointer<char> p3 = std::move(p1); // RAISES BELOW LINKER ERROR
    /* Unresolved external symbol 
    public: __cdecl MyPointer<char>::MyPointer<char>(class MyPointer<char> &&)" */

}

So p2 = p1; calls the templated copy constructor fine, but p3 = std::move(p1); can't call the templated move constructor?

So the result is that the copy constructor works, but the move constructor doesn't. Unless it's of a different type:

MyPointer<float> p1;

MyPointer<char> p2 = std::move(p1); // NOW THE TEMPLATED MOVE CONSTRUCTOR IS CALLED FINE

Can someone please help me understand why the templated move constructor is not being called?

Congenital answered 6/6, 2022 at 13:55 Comment(8)
Where is MyPointer(MyPointer&&); defined?Recoil
A template is never a copy/move constructor. maybe dupe: #55846396Zollie
@Recoil It's not defined on purpose, I don't want the compiler to call it. I don't want it auto-generated, I don't want it called, I want my templated one called.Congenital
By declaring the move constructor, it participates in overload resolution. Since it is not defined, it will cause a linker error. (And given NathanOliver's comment, the approach you want to take may not be possible.)Recoil
@Recoil If it's defined it'll call that one, i specifically don't want that called, and I would rather not redefine it (rewrite what's in the templated constructor in the non templated constructor. How about using = delete?Congenital
@Recoil But why doesn't it fail with the copy constructor? that's also not defined.Congenital
Try not declaring the copy ctor and move ctor, and see if the template constructors subsume the copy ctor and move ctor. (Which StoryTeller in NathanOliver's link said it won't be used, since templates are generated as needed. But for me, the template constructors were used rather than the compiler implicitly synthesizing them. Might be a bug in my compiler, if Storyteller is correct.)Recoil
@Recoil There is no bug. The program work as expected. In case of ` MyPointer<char> p3 = std::move(p1);` the non-template version MyPointer::MyPointer(MyPointer&&) is preferred over the corresponding templated version. But since there is no implementation available for the non-template version we get the linker error. On the other hand, for MyPointer<char> p2 = p1; the templated version is a better match than the nontemplate version MyPointer::MyPointer(const MyPointer&) as the argument passed p1 is nonconst and so the templated version is selected.Sainted
S
3

The problem is that a constructor generated from a constructor template is never a copy/move constructor.

why doesn't it fail with the copy constructor?

Because the user-declared copy ctor MyPointer::MyPointer(const MyPointer&) that you provided has a parameter of type const MyPointer& but the corresponding templated constructor that you provided has the parameter of type AnyPointerType&. Note in the latter there is no low-level const. And so when you wrote:

MyPointer<char> p2 = p1; //this will use the template constructor that has no low-level const. 

In the above, the template version is chosen over the non-template move ctor as the templated version has a non-const parameter and p1 is also nonconst. You can confirm that this is the case by making p1 const in which case you will get the same linker error but this time for the copy ctor. Demo


So the result is that the copy constructor works, but the move constructor doesn't.

The MyPointer<char> p3 = std::move(p1); doesn't work because in this case the user-declared move constructor MyPointer(MyPointer&&) that you provided has a parameter of type MyPointer&& and the corresponding templated ctor also has the parameter MyPointer&&. And so when writing:

MyPointer<char> p3 = std::move(p1); //the compiler prefers the non-template user-delcared move ctor

The non template move ctor MyPointer::MyPointer(MyPointer&&) that was user-declared is preferred over the templated version but since the user-declared ctor doesn't have an implementation we get the mentioned linker error.


Solution

I want my templated one called.

In this particular case there is a solution as shown below. In particular, we can add a low-level const in the parameter of the user declared move ctor MyPointer::MyPointer(MyPointer&&).

template <typename pointed_t>
class MyPointer
{public:
    MyPointer() : pPointed(nullptr) {}
    /* PREVENT COMPILER-GENERATED FUNCTIONS */
    MyPointer(const MyPointer&);
//------------vvvvv------------------>const added here
    MyPointer(const MyPointer&&);
    MyPointer& operator=(const MyPointer&);
    MyPointer& operator=(MyPointer&&);

    //----------
    pointed_t* pPointed;


    /* COPY CONSTRUCTOR */
    template <typename AnyPointerType>
    MyPointer(AnyPointerType& other)
    {
        std::cout<<"templated copy"<<std::endl;
    }
    /* MOVE CONSTRUCTOR */
    template <typename AnyPointerType>
    MyPointer(AnyPointerType&& other)
    {
        
       std::cout<<"template move"<<std::endl;
    }

};

int main()
{

    MyPointer<char> p1;

    MyPointer<char> p2 = p1; //calls the templated version
    MyPointer<char> p3 = std::move(p1); //calls the templated version
}

Working demo

Sainted answered 6/6, 2022 at 14:36 Comment(5)
That still leaves the problem that if I try call the copy constructor with a const value it'll give the linker error for the copy constructor, right?Congenital
@Congenital Yes that is why i said "in this particular case there is a solution". In general though there is no way to use the templated version. I have explained the reason for this which is that a constructor generated from the a constructor template is never a copy/move ctor.Sainted
Ehh, I'll rewrite the functions for the copy/move constructors assignment operators that take its own class then. Incidentally this is sort of what std::shared_ptr does, with the templated assignment operators and constructors. I wonder how it gets around this.Congenital
OK I checked the standard library documentation, std::shared_pointer defines both templated and non templated constructors.Congenital
@Congenital Yes, std::shared_ptr constructors.Sainted

© 2022 - 2024 — McMap. All rights reserved.