Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I legally cast a member function pointer to a function pointer?

I've inherited some C++ code and I've been tasked with getting rid of warnings.

Here we have a member function pointer being cast to a function pointer. I understand that member function pointers are "different" from function pointers, in that there is an implicit 'this' parameter involved under the hood. However my predecessor appears to have made explicit use of this fact, by casting from a member function pointer to a function pointer with an additional first parameter inserted.

My Questions are:

A) Can I get rid of the compiler warning?

B) To what extent is this code guaranteed to work?

I've cut it down to a small main.cpp for the purposes of this question:

#define GENERIC_FUNC_TYPE   void(*)(void)
#define FUNC_TYPE       int(*)(void *)

class MyClass
{
public:
    MyClass(int a) : memberA(a) {}
    int myMemberFunc()
    {
        return memberA;
    }

private:
    int memberA;
};

int main(int argc, char*argv[])
{
    int (MyClass::* memberFunc) () = &MyClass::myMemberFunc;
    MyClass myObject(1);
    std::cout << (myObject.*memberFunc)() << std::endl;
    // All good so far

    // Now get naughty, store it away in a very basic fn ptr
    void(*myStoredFunction)(void) = (GENERIC_FUNC_TYPE)memberFunc;  // Compiler warning

    // Reinterpret the fn pointer as a pointer to fn, with an extra object parameter
    int (*myExtractedFunction)(void*) = (FUNC_TYPE)myStoredFunction;

    // Call it
    std::cout << myExtractedFunction(&myObject) << std::endl;
}

The code compiles with one warning under g++, and as intended outputs two 1's:

main.cpp: In function ‘int main(int, char**)’:
main.cpp:27:53: warning: converting from ‘int (MyClass::*)()’ to ‘void (*)()’ [-Wpmf-conversions]
  void(*myStoredFunction)(void) = (GENERIC_FUNC_TYPE)memberFunc; // Compiler warning
                                                     ^

IMHO this code is making assumptions about the underlying mechanisms of the compiler. Or maybe these assumptions are valid for all C++ compilers - Can anyone help?

(In the actual code we're storing a whole bunch of function pointers by name in a map. These functions all have different signatures, which is why they are all cast to the same signature void(*)(void). This is analogous to the myStoredFunction above. They are then cast to the individual signatures at the point of calling, analogous to myExtractedFunction above.)

like image 201
Nigel Sharp Avatar asked Dec 14 '22 11:12

Nigel Sharp


2 Answers

How about create functions which avoid the cast entirely:

template <typename C, void (C::*M)()>
void AsFunc(void* p)
{
    (static_cast<C*>(p)->*M)();
}

then

void (*myExtractedFunction)(void*) = &AsFunc<MyClass, &MyClass::myMemberFunc>;

In C++17, with some traits, you might even have template <auto *M> void AsFunc(void* p) and void(*myStoredFunction)(void*) = &AsFunc<&MyClass::myMemberFunc>;

like image 139
Jarod42 Avatar answered Jan 19 '23 00:01

Jarod42


To answer the question in the title, no, you can't legally cast a pointer-to-member-function to a pointer-to-function. Presumably, that's what the "Compiler warning" on the line with that cast said.

A conforming compiler is required to issue a diagnostic when confronted with ill-formed code (that's a bit oversimplified), and this one did. It gave a warning. Having done that, the compiler is free to do something implementation-specific, which it seems to have done: it compiled the code into something that does what you were hoping for.

Compilers are free to represent pointers to member functions in any way that works, and for non-virtual functions, that could be just a "normal" pointer to function. But try that with a virtual function; I'll bet the consequences are more harsh.

like image 31
Pete Becker Avatar answered Jan 19 '23 01:01

Pete Becker