88 Advanced The Hundred
One Object, Two Addresses
the button that is in two places at once
struct Drawable {
virtual void draw() const { std::cout << "Drawable::draw\n"; }
virtual ~Drawable() = default;
};
struct Clickable {
virtual void click() const { std::cout << "Clickable::click\n"; }
virtual ~Clickable() = default;
};
struct Button : Drawable, Clickable {};
int main() {
Button button;
Button* b = &button;
Drawable* d = b; // the same button,
Clickable* c = b; // viewed three ways
const void* handle = c; // park its address somewhere generic
std::cout << std::boolalpha << "b == c ...... " << (b == c) << "\n";
std::cout << "b == handle . " << (b == handle) << "\n";
std::cout << "b " << static_cast<const void*>(b) << " d " << static_cast<const void*>(d)
<< " c " << static_cast<const void*>(c) << "\n";
}
Run it. Do the two checks agree?
Answer
They don't — and the last line shows why. Typical output (GCC 11 on x86-64):
Why¶
A Button holds a Drawable and a Clickable back to back, and two non-empty subobjects
cannot share an address, so at most one base can start where the Button does. Which one gets
offset 0 is the ABI's call; here Clickable begins 8 bytes in, past Drawable's vtable
pointer. b == c is still true only because comparing related pointers converts one operand
first — but a void* has no class to adjust for, so handle keeps the Clickable address.
The fix¶
const void* handle = dynamic_cast<const void*>(c); // most-derived address: now == b
static_cast<Clickable*>(b)->click(); // adds the 8: prints Clickable::click
The trap is that same call with reinterpret_cast<Clickable*>(b): no adjustment, so the call
lands on the Button address and is undefined behavior. That is the line -DSHOW_BUG adds,
and it still compiles — click() runs through Drawable's vtable and prints Drawable::draw
(GCC 11 at -O0/-O1, Clang 18 everywhere), or segfaults and drops a core file (GCC 11 at
-O2/-O3). -Wall -Wextra says nothing; Clang warns by default: -Wreinterpret-base-class,
use 'static_cast' to adjust the pointer correctly while upcasting.
Takeaway: a pointer's numeric value depends on the type you view the object through.
Try it: g++ -std=c++17 -Wall -Wextra main.cpp -o demo && ./demo; -DSHOW_BUG adds the trap.