4

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?

vbstb
  • 1,261
  • 1
  • 10
  • 14
  • 1
    Covariant pointer and reference result types are supported for overriding virtual functions. But that's all. Raw function pointers are C stuff, essentially. However, I believe you can use `std::function` for at least the covariant one. Not sure exactly of the rules enforced by `std::function`, but I think it's very permissive like, if the function pointer or functor object you provide *can be called* with arguments and result storage as specified by the template argument, then it's OK. – Cheers and hth. - Alf Sep 19 '18 at 03:57

2 Answers2

5

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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
1

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;
Bruno Romano
  • 303
  • 2
  • 10
  • 3
    This answer is extremely misleading. The only thing you can safely do after casting a function pointer is cast the result back to the original type. Trying to call the function using the wrong function pointer type is undefined behavior. – Ben Voigt Sep 19 '18 at 04:35