Search code examples
c++constructoroverload-resolution

why is the overload resolution wrong here?


suppose we have this as our setup:

#include <iostream>

class Base {
private:
    int a;
public:
    Base(int a)
        : a(a) {
    }

    virtual void print() = 0;
};

class Child : public Base {
public:

    using Base::Base;

    void print() override {
        //some code
    }
};

class Wrapper {
private:
    void* base_ptr = nullptr;
public:
    Wrapper(void* base_ptr)
        : base_ptr(base_ptr) {
        std::cout << "void* version called\n";
    }

    Wrapper(Base* const& base_ptr)
        : base_ptr(base_ptr) {
        std::cout << "Base*const& version called\n";
    }

};

if we do the following:

Child* child_ptr = new Child(1);
Wrapper w(child_ptr);

output is:

Base*const& version called

which is expected and completely normal. but if we change Wrapper(Base *const&) to Wrapper(Base*&) we call the void* version:

class Wrapper {
private:
    void* base_ptr = nullptr;
public:
    Wrapper(void* base_ptr)
        : base_ptr(base_ptr) {
        std::cout << "void* version called\n";
    }

    Wrapper(Base*& base_ptr)
        : base_ptr(base_ptr) {
        std::cout << "Base*const& version called\n";
    }

};

int main() {
    Child* child_ptr = new Child(1);
    Wrapper w(child_ptr);
    return 0;
}

for this version we get this output:

void* version called

but why is that? shouldn't Base*const& just make it also compatible with temporary objects?


Solution

  • Ok. after sometime thinking about it, I figured it out. It's simply because there is an implicit cast happening where we try to call the constructor of second version, we change the type of Child* to Base* and because of that it's not anymore an LValue and becomes an RValue, therefore it can't bind to Base*& and if we were about to change its signature to Base*&&, it would choose this overload(which is expected because of the implicit cast happening there and making it an RValue).