Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting std::function<void(Derived*)> to std::function<void(Base*)>

First, I define two classes, which inherits from one another.

class A {
};
class B : public A {
};

Then, I declare a function that uses an std::function<void(A*)> :

void useCallback(std::function<void(A*)> myCallback);

Finally, I receive a std::function of a different (but theoretically compatible) type from somewhere else that I would like to use in my callback function:

std::function<void(B*)> thisIsAGivenFunction;

useCallback(thisIsAGivenFunction);

My compiler (clang++) refuses this because the type of thisIsAGivenFunction doesn't match the expected type. But with B inheriting from A, it would make sense for thisIsAGivenFunction to be acceptable.

Should it be? If not, why? And if it should, then what am I doing wrong?

like image 259
Ecco Avatar asked Oct 16 '14 11:10

Ecco


1 Answers

Let's suppose that your class hierarchy is a little bigger:

struct A { int a; };
struct B : A { int b; };
struct C : A { int c; };

and you have functions like below:

void takeA(A* ptr)
{
    ptr->a = 1;
}

void takeB(B* ptr)
{
    ptr->b = 2;
}

Having that, we can say that takeA is callable with any instance of class derived from A (or A itself), and that takeB is callable with any instance of class B:

takeA(new A);
takeA(new B);
takeA(new C);

takeB(new B);
// takeB(new A); // error! can't convert from A* to B*
// takeB(new C); // error! can't convert from C* to B*

Now, what std::function is, it is a wrapper for callable objects. It doesn't care much about the signature of stored function object as long as that object is callable with parameters of its std::function wrapper:

std::function<void(A*)> a; // can store anything that is callable with A*
std::function<void(B*)> b; // can store anything that is callable with B*

What you are trying to do, is to convert std::function<void(B*)> to std::function<void(A*)>. In other words, you want to store callable object taking B* within wrapper class for functions taking A*. Is there an implicit conversion of A* to B*? No, there is not.

That is, one can as well call std::function<void(A*)> with a pointer to an instance of class C:

std::function<void(A*)> a = &takeA;
a(new C); // valid! C* is forwarded to takeA, takeA is callable with C*

If std::function<void(A*)> could wrap an instance of callable object taking only B*, how would you expect it to work with C*?:

std::function<void(B*)> b = &takeB;
std::function<void(A*)> a = b;
a(new C); // ooops, takeB tries to access ptr->b field, that C class doesn't have!

Fortunately, the above code does not compile.

However, doing this the opposite way is fine:

std::function<void(A*)> a = &takeA;
std::function<void(B*)> b = a;
b(new B); // ok, interface is narrowed to B*, but takeA is still callable with B*
like image 200
Piotr Skotnicki Avatar answered Oct 15 '22 08:10

Piotr Skotnicki