Date: Sun, 7 Jan 2024 20:19:17 +0000
On Sat, Jan 6, 2024 at 12:14 PM Frederick Virchanza Gotham wrote:
>
> And therefore, even on implementations that use thunks,
> "std::memfunc_to_func" should not return the address of the thunk, but
> rather it should return the address of the bonafide function. Then
> it's the job of "std::invoke_func_as_memfunc" to adjust the 'this'
> pointer where necessary.
I'm at the limit of my intelligence here trying to understand how
pointers-to-member-functions work. For the timebeing I'm focusing on
the g++ implementation that looks like this:
struct PointerToMemberFunction {
union {
void (*funcadr)(void); // always even
std::uintptr_t offset_into_vtable; // always odd
};
std::uintptr_t delta_this;
};
And I've written a function that describes a
pointer-to-member-function on g++ and clang++:
#include <string>
template<typename R, typename Class, typename... Params>
std::string DescribeMemberFunc( R (Class::*p)(Params...) )
{
using std::uintptr_t;
static_assert( sizeof(void(*)(void)) == sizeof(void*) );
static_assert( sizeof(void(*)(void)) == sizeof(uintptr_t) );
static_assert( sizeof(p) == 2u*sizeof(uintptr_t) );
auto &n = *static_cast<uintptr_t*>(static_cast<void*>(&p));
std::string s("PMF is ");
if ( n & 1u )
{
// n is an offset into a vtable
--n;
s += "vtable offset " + std::to_string(n /
sizeof(void(*)(void))) + " with 'this' + ";
}
else
{
// n is just a pointer to a function
s += "func pointer with 'this' + ";
}
auto this_offset = (&n)[1u];
s += std::to_string(this_offset);
s += '.';
return s;
}
And I've written some test code:
https://godbolt.org/z/sEEn9n64h
In my example code, the address of a HouseBoat is a few bytes less
than the address of a Residence, and therefore I'd expect to see some
adjustment of the 'this' pointer -- but surprisingly the
pointer-to-member-function has the 'delta_this' set to 0. It seems
that the 'this' pointer gets adjusted when you actually invoke the
pointer-to-member-function on an object (and it seems that the 'delta
this' is gotten from the type of the pointer-to-member function).
What I don't understand though . . . . is if the 'delta this' can be
gotten from the type of the pointer-to-member-function, then why are
pointers to member functions 128-Bit with a 64 bits for the 'delta
this' if it's always going to be zero?
But anyway . . . if we are to retain the class type when calling
"memfunc_to_func", then we'll have to put the class type in as the
first parameter, something like:
int (MyClass::*pmf)(double) = &MyClass::SomeMethod;
int (*pf)(MyClass*,double) = std::memfunc_to_func(p);
But "pf" is not intended to be used as a normal function. Instead you must do:
std::invoke_func_as_memfunc(pf, &myvar, 56.7L);
If we don't put that extra parameter in there, then instead we would
need to tell 'invoke_func_as_memfunc' the class type, perhaps
something like:
int (MyClass::*pmf)(double) = &MyClass::SomeMethod;
int (*pf)(double) = std::memfunc_to_func(p);
std::invoke_func_as_memfunc<MyClass>(pf, &myvar, 56.7L); //
note the template parameter here
>
> And therefore, even on implementations that use thunks,
> "std::memfunc_to_func" should not return the address of the thunk, but
> rather it should return the address of the bonafide function. Then
> it's the job of "std::invoke_func_as_memfunc" to adjust the 'this'
> pointer where necessary.
I'm at the limit of my intelligence here trying to understand how
pointers-to-member-functions work. For the timebeing I'm focusing on
the g++ implementation that looks like this:
struct PointerToMemberFunction {
union {
void (*funcadr)(void); // always even
std::uintptr_t offset_into_vtable; // always odd
};
std::uintptr_t delta_this;
};
And I've written a function that describes a
pointer-to-member-function on g++ and clang++:
#include <string>
template<typename R, typename Class, typename... Params>
std::string DescribeMemberFunc( R (Class::*p)(Params...) )
{
using std::uintptr_t;
static_assert( sizeof(void(*)(void)) == sizeof(void*) );
static_assert( sizeof(void(*)(void)) == sizeof(uintptr_t) );
static_assert( sizeof(p) == 2u*sizeof(uintptr_t) );
auto &n = *static_cast<uintptr_t*>(static_cast<void*>(&p));
std::string s("PMF is ");
if ( n & 1u )
{
// n is an offset into a vtable
--n;
s += "vtable offset " + std::to_string(n /
sizeof(void(*)(void))) + " with 'this' + ";
}
else
{
// n is just a pointer to a function
s += "func pointer with 'this' + ";
}
auto this_offset = (&n)[1u];
s += std::to_string(this_offset);
s += '.';
return s;
}
And I've written some test code:
https://godbolt.org/z/sEEn9n64h
In my example code, the address of a HouseBoat is a few bytes less
than the address of a Residence, and therefore I'd expect to see some
adjustment of the 'this' pointer -- but surprisingly the
pointer-to-member-function has the 'delta_this' set to 0. It seems
that the 'this' pointer gets adjusted when you actually invoke the
pointer-to-member-function on an object (and it seems that the 'delta
this' is gotten from the type of the pointer-to-member function).
What I don't understand though . . . . is if the 'delta this' can be
gotten from the type of the pointer-to-member-function, then why are
pointers to member functions 128-Bit with a 64 bits for the 'delta
this' if it's always going to be zero?
But anyway . . . if we are to retain the class type when calling
"memfunc_to_func", then we'll have to put the class type in as the
first parameter, something like:
int (MyClass::*pmf)(double) = &MyClass::SomeMethod;
int (*pf)(MyClass*,double) = std::memfunc_to_func(p);
But "pf" is not intended to be used as a normal function. Instead you must do:
std::invoke_func_as_memfunc(pf, &myvar, 56.7L);
If we don't put that extra parameter in there, then instead we would
need to tell 'invoke_func_as_memfunc' the class type, perhaps
something like:
int (MyClass::*pmf)(double) = &MyClass::SomeMethod;
int (*pf)(double) = std::memfunc_to_func(p);
std::invoke_func_as_memfunc<MyClass>(pf, &myvar, 56.7L); //
note the template parameter here
Received on 2024-01-07 20:19:24