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

余生长醉 提交于 2019-12-08 14:53:07

问题


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?


回答1:


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*



回答2:


You can't pass &Foo(Apple) when somebody may pass you a random Fruit including a Pear.




回答3:


It works but in opposite direction:

struct A {};
struct B: A {};

struct X {};
struct Y: X {};

static X useCallback(std::function<X(B)> callback) {
    return callback({});
}

static Y cb(A) {
    return {};
}

int main() {
    useCallback(cb);
}

The signature of callback declares what will be passed to it and what is to be got back. Specific callback can take less specific types if doesn't care too much about them. Similarly it can return more specific type, extra information will be stripped. Refer to covariant vs contravariant types (input/output in simplified wording).



来源:https://stackoverflow.com/questions/26403556/converting-stdfunctionvoidderived-to-stdfunctionvoidbase

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!