14

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?

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
Ecco
  • 1,323
  • 1
  • 11
  • 15
  • 1
    possible duplicate of [C++ Templates polymorphism](http://stackoverflow.com/questions/2203388/c-templates-polymorphism) – Samuel Oct 16 '14 at 11:50
  • 3
    it is not duplicate, `std::function` supports such *conversions*. but in the other way, you can convert `std::function` to `std::function`, because function that operates on `A*` can do the same when it receives an instance of `B*`, but not the other way – Piotr Skotnicki Oct 16 '14 at 11:51
  • The source of your problem is using `std::function` as a callback instead of accepting a `Functor` as a template argument. Unless you have a good reason to do this, don't do it. – pmr Oct 16 '14 at 17:28
  • 1
    What does your callback pass to your `std::function`? What if it passes a pointer to `class C:public A{}` to it, what does `thisIsAGivenFunction` do then? Please answer both questions, neither are rhetorical. – Yakk - Adam Nevraumont Oct 16 '14 at 20:08

3 Answers3

18

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*
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
2

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

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I don't get it. In my case my function `useCallback` expects a random `Fruit`, and when I'm passing it a `Pear` the compiler complains. – Ecco Oct 16 '14 at 11:53
  • 1
    @Ecco. I think this is that your `useCallback` expects to call a function with a `Fruit` (could be `Pear` or `Apple`) and you are giving it a function that expects at least a `Pear`. – Niall Oct 16 '14 at 11:58
  • 2
    Niall is right. `useCallback` may give your `thisIsAGivenFunction` an `A*` pointer to a `C` object. Your function has to accept **all** `A` objects, not a subset. – MSalters Oct 16 '14 at 12:05
2

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).

  • Right. See contravariance in std::function http://cpptruths.blogspot.com/2015/11/covariance-and-contravariance-in-c.html#function_contravariance – Sumant Nov 18 '15 at 23:34