Подтвердить что ты не робот

Преобразование std:: function <void (Derived *)> в std:: function <void (Base *)>

Сначала я определяю два класса, которые наследуются друг от друга.

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

Затем я объявляю функцию, которая использует std::function<void(A*)>:

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

Наконец, я получаю std::function другого (но теоретически совместимого) типа из другого места, который я хотел бы использовать в моей функции обратного вызова:

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

useCallback(thisIsAGivenFunction);

Мой компилятор (clang++) отказывается от этого, потому что тип thisIsAGivenFunction не соответствует ожидаемому типу. Но с B, наследующим от A, было бы разумно, чтобы thisIsAGivenFunction был приемлемым.

Должно ли это быть? Если нет, то почему? И если это так, то что я делаю неправильно?

4b9b3361

Ответ 1

Предположим, что ваша иерархия классов немного больше:

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

и у вас есть функции, как показано ниже:

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

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

При этом можно сказать, что takeA можно вызывать с любым экземпляром класса, производным от A (или A), и что takeB может быть вызван с любым экземпляром класса 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*

Теперь, что std::function, это оболочка для вызываемых объектов. Его не волнует подпись подписи хранимого функционального объекта до тех пор, пока этот объект может быть вызван с параметрами его обертки std::function:

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*

Что вы пытаетесь сделать, это преобразовать std::function<void(B*)> в std::function<void(A*)>. Другими словами, вы хотите сохранить вызываемый объект, принимающий B* в классе оболочки для функций, принимающих A*. Существует ли неявное преобразование A* в B*? Нет, нет.

То есть, можно также вызвать std::function<void(A*)> с указателем на экземпляр класса C:

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

Если std::function<void(A*)> может обернуть экземпляр вызываемого объекта только с помощью B*, как вы ожидаете, что он будет работать с 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!

К счастью, приведенный выше код не компилируется.

Тем не менее, делать это противоположным образом:

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

Вы не можете передать &Foo(Apple), когда кто-то может передать вам случайный Fruit, включая Pear.

Ответ 3

Он работает, но в противоположном направлении:

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);
}

Подпись обратного вызова объявляет то, что будет передано ей и что нужно вернуть. Конкретный обратный вызов может принимать менее конкретные типы, если их не волнует. Точно так же он может вернуть более конкретный тип, дополнительная информация будет удалена. Обратитесь к ковариантным и контравариантным типам (ввод/вывод в упрощенной формулировке).