【问题标题】:Converting std::function<void(Derived*)> to std::function<void(Base*)>将 std::function<void(Derived*)> 转换为 std::function<void(Base*)>
【发布时间】:2014-12-11 18:29:03
【问题描述】:

首先,我定义了两个类,它们相互继承。

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

然后,我声明一个使用 std::function&lt;void(A*)&gt; 的函数:

void useCallback(std::function&lt;void(A*)&gt; myCallback);

最后,我从其他地方收到了一个不同(但理论上兼容)类型的std::function,我想在我的回调函数中使用它:

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

useCallback(thisIsAGivenFunction);

我的编译器 (clang++) 拒绝这样做,因为 thisIsAGivenFunction 的类型与预期的类型不匹配。但是由于B 继承自A,所以thisIsAGivenFunction 是可以接受的。

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

【问题讨论】:

  • C++ Templates polymorphism 的可能重复项
  • 不重复,std::function 支持这样的转换。但另一方面,您可以将std::function&lt;void(A*)&gt; 转换为std::function&lt;void(B*)&gt;,因为在A* 上运行的函数在接收到B* 的实例时可以执行相同的操作,但不是其他方式
  • 问题的根源是使用 std::function 作为回调,而不是接受 Functor 作为模板参数。除非你有充分的理由这样做,否则不要这样做。
  • 您的回调将什么传递给您的std::function?如果它将指向class C:public A{} 的指针传递给它,那thisIsAGivenFunction 会做什么呢?请回答这两个问题,都不是修辞。

标签: c++ c++11 std-function


【解决方案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&lt;void(B*)&gt; 转换为std::function&lt;void(A*)&gt;。换句话说,您希望将采用B* 的可调用对象存储在采用A* 的函数的包装类中。是否存在 A*B* 的隐式转换?不,没有。

也就是说,也可以使用指向类C 实例的指针调用std::function&lt;void(A*)&gt;

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

如果std::function&lt;void(A*)&gt; 可以包装一个只采用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】:

    当有人可能给你一个随机的Fruit 包括Pear 时,你不能传递&amp;Foo(Apple)

    【讨论】:

    • 我不明白。在我的情况下,我的函数 useCallback 需要一个随机的 Fruit,当我传递它时,编译器会抱怨 Pear
    • @Ecco。我认为这是你的useCallback 期望调用一个带有Fruit 的函数(可能是PearApple)并且你给它一个至少需要Pear 的函数。
    • 尼尔是对的。 useCallback 可能会给你的 thisIsAGivenFunction 一个 A* 指向 C 对象的指针。您的函数必须接受 all A 对象,而不是子集。
    【解决方案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);
    }
    

    回调的签名声明了将传递给它的内容以及要返回的内容。如果不太关心它们,特定的回调可以采用较少的特定类型。同样它可以返回更具体的类型,额外的信息将被剥离。请参阅协变与逆变类型(简化措辞的输入/输出)。

    【讨论】:

    猜你喜欢
    • 2023-02-07
    • 1970-01-01
    • 1970-01-01
    • 2017-07-08
    • 2018-02-26
    • 1970-01-01
    • 2019-02-20
    • 2019-06-05
    • 2018-08-24
    相关资源
    最近更新 更多