【问题标题】:Covariance and contravariance in C++ function pointer?C ++函数指针中的协变和逆变?
【发布时间】:2018-09-19 03:41:02
【问题描述】:

考虑一下

class Base { };

class Derived : public Base { };

Base *f1(Derived *) { return {}; }
Derived *f2(Derived *) { return {}; }   // covariant
Base *f3(Base *) { return {}; } // contravariant
Derived *f4(Base *) { return {}; } // covariant & contravariant

using Callback = Base *(*)(Derived *);

Callback pfunc1 = f1;   // works of course

// These won't work...
Callback pfunc2 = f2;
Callback pfunc3 = f3;
Callback pfunc4 = f4;


// So I have to make a wrapper for each
Base *f2_wrap(Derived *d)
{
    return f2(d);
}

Base *f3_wrap(Derived *d)
{
    return f3(d);
}

Base *f4_wrap(Derived *d)
{
    return f4(d);
}

// Now it works
Callback pfunc2 = f2_wrap;
Callback pfunc3 = f3_wrap;
Callback pfunc4 = f4_wrap;

那么为什么我不能将函数指针设置为以Derived 对象作为返回值的函数,或以Base 对象作为参数的函数(在c# 委托中有效)?

我知道我可以使用包装函数,但为什么它不是语言功能的一部分?

【问题讨论】:

  • 协变指针和引用结果类型支持覆盖虚函数。但仅此而已。原始函数指针本质上是 C 的东西。但是,我相信您至少可以将std::function 用于协变。不确定std::function 强制执行的规则的确切内容,但我认为这是非常宽松的,例如,如果您提供的函数指针或仿函数对象可以使用模板参数指定的参数和结果存储来调用 ,那么就OK了。

标签: c++ oop function-pointers covariance contravariance


【解决方案1】:

原始函数指针只有在类型完全匹配时才兼容赋值。

但是,你可以使用std::function:

#include <functional>

struct Base{};
struct Derived: Base{};

auto f1( Derived* ) -> Base*        { return 0; }
auto f2( Derived* ) -> Derived*     { return 0; }   // covariant
auto f3( Base* )    -> Base*        { return 0; }   // contravariant
auto f4( Base* )    -> Derived*     { return 0; }   // covariant & contravariant

auto main()
    -> int
{
    using Callback = std::function<auto( Derived* ) -> Base*>;

    Callback pfunc1 = f1;   // works
    Callback pfunc2 = f2;   // works
    Callback pfunc3 = f3;   // works
    Callback pfunc4 = f4;   // works
}

重写虚函数的规则不太宽松:支持原始指针和引用类型的协变结果,但仅此而已。没有逆变换。

#include <functional>

struct Base{};
struct Derived: Base{};

struct F{ virtual auto f( Derived* ) -> Base* = 0; };

#define R override { return 0; }
struct F1: F { auto f( Derived* ) -> Base*      R };
struct F2: F { auto f( Derived* ) -> Derived*   R };   // covariant, OK
struct F3: F { auto f( Base* )    -> Base*      R };   // !contravariant
struct F4: F { auto f( Base* )    -> Derived*   R };   // !covariant & contravariant

MinGW g++ 7.3.0编译结果:

> g++ -c 2.cpp 2.cpp:11:21:错误:'Base* F3::f(Base*)' 标记为'覆盖',但不覆盖 结构 F3: F { auto f( Base* ) -> Base* R }; // !逆变 ^ 2.cpp:12:21:错误:'Derived* F4::f(Base*)' 标记为'覆盖',但不覆盖 结构 F4: F { auto f( Base* ) -> Derived* R }; // !协变和逆变

对原始指针和引用结果类型的协方差限制在实践中不是问题。例如,具有智能指针结果的明显协变函数可以很容易地表示为调用具有原始指针结果的虚函数的非虚拟重载。在实践中,缺乏对逆变器的支持同样不是问题,但原因很简单,人们永远不需要它。

【讨论】:

    【解决方案2】:

    C++ 被设计为以这种方式工作,不接受不同的参数,即使它是派生类。 看看这个帖子Cast function pointers that differs by argument type

    C#有虚拟机,不允许多重继承,所以C#在这种情况下控制权比较多,C#和C++没法比。

    如果你想使用强制转换,你可以这样做,但是你假设派生类和基类有任何错误,如果你做错了,它会导致你的程序崩溃。

    class Base { };
    
    class Derived : public Base { };
    
    Base *f1(Derived *) { return {}; }
    Derived *f2(Derived *) { return {}; }   // covariant
    Base *f3(Base *) { return {}; } // contravariant
    Derived *f4(Base *) { return {}; } // covariant & contravariant
    
    using Callback = Base *(*)(Derived *);
    
    Callback pfunc1 = f1;   // works of course
    
    // These won't work (now it will work)...
    Callback pfunc2 = (Callback)(f2); //explict cast
    Callback pfunc3 = (Callback)f3;
    Callback pfunc4 = (Callback)f4;
    

    【讨论】:

    • 这个答案极具误导性。转换函数指针后唯一可以安全地做的就是将结果转换回原始类型。尝试使用错误的函数指针类型调用函数是未定义的行为。
    猜你喜欢
    • 2012-04-06
    • 1970-01-01
    • 1970-01-01
    • 2015-01-16
    • 2013-07-11
    • 2023-03-07
    • 1970-01-01
    • 2015-02-09
    • 1970-01-01
    相关资源
    最近更新 更多