2014-10-16 112 views
12

首先,我定义了两个相互继承的类。将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); 

我的编译器(铛++)拒绝这个,因为类型与预期类型不匹配。但从A继承的BthisIsAGivenFunction是可以接受的。

应该是?如果不是,为什么?如果它应该,那么我做错了什么?

+1

可能重复[C++模板多态](http://stackoverflow.com/questions/2203388/c-templates-polymorphism) – Samuel 2014-10-16 11:50:16

+3

它不重复,'std :: function'支持这种*转换*。但换句话说,您可以将'std :: function '转换为'std :: function ',因为在'A *'上运行的函数在接收到'B *'的实例时可以做同样的事情,但是而不是其他方式 – 2014-10-16 11:51:20

+0

问题的根源在于使用'std :: function'作为回调函数,而不是接受'Functor'作为模板参数。除非你有充分的理由这样做,否则不要这样做。 – pmr 2014-10-16 17:28:55

回答

2

当有人可能会传递给您随机Fruit包括Pear时,您无法通过&Foo(Apple)

+0

我不明白。在我的情况下,我的函数'useCallback'需要一个随机的'Fruit',当我传递一个'Pear'时,编译器会抱怨。 – Ecco 2014-10-16 11:53:50

+1

@Ecco。我认为这是你的'useCallback'期望用一个'Fruit'(可能是'Pear'或'Apple')来调用一个函数,并且你给它一个至少需要一个'Pear'的函数。 – Niall 2014-10-16 11:58:11

+2

Niall是对的。 'useCallback'可能会给''thisIsagivenFunction''指向'C'对象的'A *'指针。你的函数必须接受**所有**'A'对象,而不是子集。 – MSalters 2014-10-16 12:05:50

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 2015-11-18 23:34:42

相关问题