C++函数指针的协变和逆变?

vbs*_*stb 4 c++ oop function-pointers covariance contravariance

考虑一下这个

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;
Run Code Online (Sandbox Code Playgroud)

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

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

Che*_*Alf 5

仅当类型完全匹配时,原始函数指针才兼容赋值。

但是,您可以使用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
}
Run Code Online (Sandbox Code Playgroud)

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

#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
Run Code Online (Sandbox Code Playgroud)

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*)' 标记为'覆盖',但不覆盖
 struct F4: F { auto f( Base* ) -> Derived* R }; // !协变和逆变

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