Covariance and contravariance in C++ function pointer?

Viewed 1348

Consider this

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;

So why can't I set a function pointer to a function that has a Derived object as return value, or a function that has a Base object as an argument (which works in c# delegate)?

I know I can get around using a wrapper function, but why isn't it part of a language feature?

2 Answers

Raw function pointers are only assignment compatible when the types match exactly.

However, you can use 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
}

The rules for overriding virtual functions are less permissive: covariant results of raw pointer and reference type are supported, but that's all. No contra-variance.

#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

Compilation result with MinGW g++ 7.3.0:

> g++ -c 2.cpp
2.cpp:11:21: error: 'Base* F3::f(Base*)' marked 'override', but does not override
 struct F3: F { auto f( Base* )    -> Base*      R };   // !contravariant
                     ^
2.cpp:12:21: error: 'Derived* F4::f(Base*)' marked 'override', but does not override
 struct F4: F { auto f( Base* )    -> Derived*   R };   // !covariant & contravariant

The restriction to raw pointer and reference result types for covariance, is not a problem in practice. For example, an apparently covariant function with smart pointer result can be easily expressed as a non-virtual overload calling a virtual function with raw pointer result. The lack of support for contra-variance is likewise not a problem in practice, but for the simple reason that one ~never needs it.

C++ was design to work this way, to not accept a different parameter., even if it is a derived class. Take a look at this thread Cast function pointers that differs by argument type

C# has a virtual machine and doesnt allow multiple inheritance, so C# has a lot of more control in this kind of situation, you cant compare C# and C++.

If you want to use cast, you can do in this way, but you are assuming any mistake about derived and base class, if you do something wrong, it will crash your program.

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;
Related