I was reading the article Member Function Pointers and the Fastest Possible C++ Delegates from Don Clugston and was experimenting with this stuff myself and was not able to reproduce a case correctly.
Of course, the code from Don Clugston is undefined behaviour.
This is specifically about GCC's representation of member function pointers.
Here's a code snippet from the article about the GCC member function representation (copied as is from the article, not actual code, don't even compile):
// GNU g++ uses a tricky space optimisation, also adopted by IBM's VisualAge and XLC. struct GnuMFP { union { CODEPTR funcadr; // always even int vtable_index_2; // = vindex*2+1, always odd }; int delta; }; adjustedthis = this + delta if (funcadr & 1) CALL (* ( *delta + (vindex+1)/2) + 4) else CALL funcadr
Of course, the standard says nothing about this. Also, GCC ABI might have changed a lot since the article was written. However, I'm not interested in the standard or defined behaviour. I am interested in the current ABI and what the compiler do.
The problem is that I haven't been able to produce a member function pointer that fills the delta value for me to experiment with it.
I assumed something similar to delta still exist since the size of a member function pointer is still the size of two pointer. Also, according to my observations, the vtable index trick still apply today.
Here's what I tried:
#include <cstring>
#include <iostream>
#include <iomanip>
void print_pointer(auto const ptr) {
alignas(alignof(ptr)) std::byte memory[sizeof(ptr)];
std::memcpy(memory, std::addressof(ptr), sizeof(ptr));
auto until_newline = int{8};
for (auto const b : memory) {
std::cout << std::hex << std::setfill('0') << std::setw(2) << static_cast<std::uint16_t>(b);
if (--until_newline == 0) {
until_newline = 8;
std::cout << '\n';
}
}
}
// No inheritance, simplest possible
namespace test1 {
struct S {
char a;
void method() const& {
std::cout << "test1 S";
}
};
}
// Simple inheritance, non polymorphic
namespace test2 {
struct B1 { char a; };
struct S : B1 {
char a;
void method() const& {
std::cout << "test1 S";
}
};
}
// Multiple inheritance, non polymorphic
namespace test3 {
struct B1 { char a; };
struct B2 { char a; };
struct S : B1, B2 {
char a;
void method() const& {
std::cout << "test1 S";
}
};
}
// Multiple inheritance, non polymorphic, function in the middle
namespace test4 {
struct B1 { char a; };
struct B2 {
char a;
void method() const& {
std::cout << "test1 S";
}
};
struct S : B1, B2 { char a; };
}
// Simple inheritance, polymorphic
namespace test5 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct S : B1 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Multiple inheritance, polymorphic, one base only
namespace test6 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct B2 {
char a;
};
struct S : B1, B2 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Multiple inheritance, polymorphic, two base
namespace test7 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct B2 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct S : B1, B2 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Simple virtual inheritance, non polymorphic
namespace test8 {
struct B1 { char a; };
struct S : virtual B1 {
void method() const& {
std::cout << "test1 S";
}
};
}
// Simple virtual inheritance, polymorphic
namespace test9 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct S : virtual B1 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Multiple with one virtual inheritance, one polymorphic
namespace test10 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct B2 {
char a;
};
struct S : B1, virtual B2 {
void method() const& override {
std::cout << "test1 S";
}
};
}
// Multiple with both virtual inheritance, both polymorphic
namespace test11 {
struct B1 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct B2 {
char a;
virtual void method() const& {
std::cout << "test1 S";
}
};
struct S : virtual B1, virtual B2 {
void method() const& override {
std::cout << "test1 S";
}
};
}
int main() {
print_pointer(&test1::S::method);
std::cout << '\n';
print_pointer(&test2::S::method);
std::cout << '\n';
print_pointer(&test3::S::method);
std::cout << '\n';
print_pointer(&test4::S::method);
std::cout << '\n';
print_pointer(&test5::S::method);
std::cout << '\n';
print_pointer(&test6::S::method);
print_pointer(&test6::B1::method);
std::cout << '\n';
print_pointer(&test7::S::method);
print_pointer(&test7::B1::method);
print_pointer(&test7::B2::method);
std::cout << '\n';
print_pointer(&test8::S::method);
std::cout << '\n';
print_pointer(&test9::S::method);
print_pointer(&test9::B1::method);
std::cout << '\n';
print_pointer(&test10::S::method);
print_pointer(&test10::B1::method);
std::cout << '\n';
print_pointer(&test11::S::method);
print_pointer(&test11::B1::method);
print_pointer(&test11::B2::method);
}
In all of my example, the last 8 byte of the member function pointer is 0000000000000000
Here's the complete output:
b013400000000000 0000000000000000
f013400000000000 0000000000000000
3014400000000000 0000000000000000
d013400000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
1014400000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
0100000000000000 0000000000000000
How can I produce a member function pointer with a non zero delta on GCC?