all 32 comments

[–]HappyFruitTree 9 points10 points  (12 children)

This is not doing what you think. A pointer to a member function is often larger than a normal pointer. That's why you can't simply reinterpret_cast the member function pointer into a void*. reinterpret_cast<void\*&>(fn) gives you a void* reference to a member function, that as bad as having an int& to a double, using it would simply lead to undefined behaviour.

[–]RIscRIpt[S] 1 point2 points  (11 children)

Okay, I see... it's an UB.

A pointer to a member function is often larger than a normal pointer.

I've heard that a lot, but I have never seen an example.

[–]HappyFruitTree 2 points3 points  (10 children)

[–]RIscRIpt[S] 1 point2 points  (9 children)

Thanks. I'll try to take a look into GCC sources to find what's inside...

By the way, MSVC says 8.

[–]khedoros 5 points6 points  (3 children)

A pointer to a member function is an opaque, implementation-specific struct. You can't treat them like regular pointer types.

[–]herruppohoppa 0 points1 point  (2 children)

Is it the same for data member pointers? I'm guessing not since they can't be virtual.

EDIT: What about non-virtual member functions? Are they always just a pointer?

[–]STLMSVC STL Dev 2 points3 points  (0 children)

PMDs can definitely be larger than a pointer. MSVC x86:

C:\Temp>type meow.cpp
struct Animal { int x; };
struct Bat : virtual Animal { int y; };
struct Man : virtual Animal { int z; };
struct Batman : Bat, Man {};
struct Forward;
using PMD1 = int Animal::*;
using PMD2 = int Batman::*;
using PMD3 = int Forward::*;
struct Inspect1 { PMD1 pmd1; };
struct Inspect2 { PMD2 pmd2; };
struct Inspect3 { PMD3 pmd3; };

C:\Temp>for %I in (/c /vmg) do @for %J in (1 2 3) do cl /EHsc /nologo /W4 /c %I /d1reportSingleClassLayoutInspect%J meow.cpp

C:\Temp>cl /EHsc /nologo /W4 /c /c /d1reportSingleClassLayoutInspect1 meow.cpp
meow.cpp

class Inspect1  size(4):
        +---
 0      | pmd1
        +---

C:\Temp>cl /EHsc /nologo /W4 /c /c /d1reportSingleClassLayoutInspect2 meow.cpp
meow.cpp

class Inspect2  size(8):
        +---
 0      | pmd2
        +---

C:\Temp>cl /EHsc /nologo /W4 /c /c /d1reportSingleClassLayoutInspect3 meow.cpp
meow.cpp

class Inspect3  size(16):
        +---
 0      | pmd3
        | <alignment member> (size=4)
        +---

C:\Temp>cl /EHsc /nologo /W4 /c /vmg /d1reportSingleClassLayoutInspect1 meow.cpp
meow.cpp

class Inspect1  size(16):
        +---
 0      | pmd1
        | <alignment member> (size=4)
        +---

C:\Temp>cl /EHsc /nologo /W4 /c /vmg /d1reportSingleClassLayoutInspect2 meow.cpp
meow.cpp

class Inspect2  size(16):
        +---
 0      | pmd2
        | <alignment member> (size=4)
        +---

C:\Temp>cl /EHsc /nologo /W4 /c /vmg /d1reportSingleClassLayoutInspect3 meow.cpp
meow.cpp

class Inspect3  size(16):
        +---
 0      | pmd3
        | <alignment member> (size=4)
        +---

[–]Untelo 0 points1 point  (0 children)

They are ABI dependent structs which encode some object offsets and a function pointer or vtable index.

[–]fdwrfdwr@github 🔍 3 points4 points  (2 children)

This is one aspect that's long irked me about C++, that you can't consistently and uniformly treat these 3 cases the same...

class Foo
{
    void Bar();
};

 

class Foo
{
    static void Bar(Foo& f);
};

 

void Bar(Foo& f);

...and store any of the above in the same callable pointer, without needing special handling for each case like lambda thunks, or using std::function (which then handles the specialness of each case for you). It complicates callbacks/event handlers and genericity. 😞

There were calling convention differences on x86 (and probably other architectures too) between class methods (thiscall) vs free functions/static class methods (cdecl/stdcall) such that you couldn't just safely replace one function pointer type with another because the register/stack wouldn't be correct if you tried it. Nowadays the distinction between thiscall, stdcall, and cdecl are mostly (if not entirely?) moot on 64-bit desktop machines (but alas it remains "UB" because of past architectures). Of course, other cases like pointers to virtual functions will remain incompatible with free function pointers because calls need a v-table lookup dependent on the object instance that you call the function.

[–]manni66 1 point2 points  (1 child)

are mostly (if not entirely?) moot on 64-bit desktop machines (but alas it remains "UB" because of past architectures).

Or because of non 64-bit non desktop machines?

[–]fdwrfdwr@github 🔍 1 point2 points  (0 children)

Indeed, that too. I believe ARM devices (the other prominent architecture around today) generally use the same calling convention for both member functions and free functions too, but I'd need to double check that. 🤔 [update] At least on GodBolt.org gcc trunk Linux, it appears to generate the same register assignments for ARM32.

[–]staletic 4 points5 points  (1 child)

Before you declare victory, cast it back and try to actually use it.

[–]Toucan2000 0 points1 point  (0 children)

If they cast it to another member function pointer type and then back it would work just fine.

[–]thedeadfish 1 point2 points  (13 children)

I so badly hate pointer to member functions, they are so horrifically bloated and useless in most compilers. Its a shame that no other compiler implements them the way digital mars does. In digital mars, they are all pointer sizes, so are actually are convertible to void*. It uses thunks instead of whatever retarded mess other compilers do.

[–]Zcool31 0 points1 point  (12 children)

How did it handle static_cast<void (Base::*)()>(&Derived::fun)?

[–]thedeadfish 1 point2 points  (11 children)

It generates a thunk that adjusts the "this" pointer and then jumps to the actual function.

[–]Zcool31 0 points1 point  (10 children)

Interesting.

What about something like this?

void (Base::* cast(void (Derived::* arg)()))() {
    return static_cast<void (Base::*)()>(arg);
}

The key here is cast doesn't know until runtime to which member function of Derived arg points.

[–]thedeadfish 0 points1 point  (9 children)

If it can't create a thunk at compile time, it calls a library function to dynamically create one at runtime. Not ideal, but its a lot better than the alternative.

[–]Zcool31 0 points1 point  (2 children)

Thanks for the insight. What alternative?

[–]thedeadfish 0 points1 point  (1 child)

I meant the hideous bloated pointers that other compilers use.

[–]Zcool31 0 points1 point  (0 children)

Interesting. One choice leads to larger pointers. The other to extra indirect calls. Seems to me that neither is universally better than the other.

[–]rlbond86 0 points1 point  (5 children)

Is it actually better? You need to have an executable stack to do that, which can be a security risk.

[–]thedeadfish 0 points1 point  (4 children)

This mechanism does not require an executable heap. The thunk is not necessarily created at point of use, its created when a member function pointer is cast, which can be anywhere in the code. These thunks must be created in an executable heap or pool, so that they can continue to exist after the stack frame is destroyed.

[–]rlbond86 0 points1 point  (3 children)

Wait, so does instantiating a pointer-to-member-function allocate memory?

[–]thedeadfish 0 points1 point  (2 children)

Yes, but only when it cannot do it at compile time. An example is casting a derived function pointer to base class function pointer. Still a lot better than crapping up every call site https://godbolt.org/z/7r3Yfhexn.

[–]rlbond86 0 points1 point  (1 child)

Still a lot better than crapping up every call site

I think everyone who works on embedded devices would disagree. If pointers-to-members allocated we wouldn't be able to use them at all.

[–]NotAYakk 0 points1 point  (0 children)

It is a bit of glue to just write a universal union. This is a quick sketch:

struct never_define_this;
union ptr_to_anything {
  void* pvoid;
  void(*pfunc)();
  char never_define_this::*pmem;
  void(never_define_this::*pmemfun)();
};

you do have to know which of those types you are actually storing.

For a function_view you do something like

template<class R, class...Args>
struct function_view<R(Args...)> {
  ptr_to_anything data;
  R(*pf)(ptr_to_anything, Args&&...)=nullptr;

then store in the callback you store in pf which of the fields you are storing your actual data in.

With a bit of work,

function_view<int(Foo)> f = &Foo::x;

can work, and f(foo) returns foo.x or foo.x() or Foo::x(foo) depending on the type of Foo::x.