2014-10-16 3 views
12

Сначала я определяю два класса, которые наследуются друг от друга.Преобразование std :: function <void (Derived *)> to 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.

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

+1

Возможный дубликат [Полиморфизм шаблонов C++] (http://stackoverflow.com/questions/2203388/c-templates-polymorphism) – Samuel

+3

Это не дубликат, 'std :: function' поддерживает такие * преобразования *. но, наоборот, вы можете преобразовать 'std :: function ' в 'std :: function ', потому что функция, которая работает с 'A *', может сделать то же самое, когда получает экземпляр 'B *', но а не наоборот –

+0

Источником вашей проблемы является использование 'std :: function' в качестве обратного вызова вместо принятия« Functor »в качестве аргумента шаблона. Если у вас нет веских оснований для этого, не делайте этого. – pmr

ответ

2

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

+0

Я не понимаю. В моем случае моя функция 'useCallback' ожидает случайный« Fruit », и когда я передаю ему« Pear », компилятор жалуется. – Ecco

+1

@Ecco. Я думаю, это означает, что ваш 'useCallback' ожидает вызова функции с помощью« Fruit »(может быть« Pear »или« Apple »), и вы даете ей функцию, которая ожидает хотя бы« Pear ». – Niall

+2

Найл прав. 'useCallback' может дать вашему' thisIsAGivenFunction' указатель 'A *' объекту 'C'. Ваша функция должна принимать ** все ** 'A' объекты, а не подмножество. – MSalters

14

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

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 является , вызываемым с любым экземпляром cl задница 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

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

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

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

+0

Справа. См. Контравариантность в std :: function http://cpptruths.blogspot.com/2015/11/covariance-and-contravariance-in-c.html#function_contravariance – Sumant

Смежные вопросы