Skip to main content

Command Palette

Search for a command to run...

The hidden detail behind std::addressof in C++

Updated
3 min read
A
I write about C++, performance, and building things from first principles. Currently exploring data structures, allocators, and how things work under the hood.

In c++, even something like &obj is not always what it seems.

Normally, when we write:

struct Resource {
    int data = 42;
};

int main() {
    Resource r;
    auto h = &r;
    std::cout << h << '\n'; 
}

we expect &r to give us the memory address of r, and in most cases, it does.
But C++ allows something interesting, operators can be overloaded.


When & is Overloaded:

Let’s modify the example:

#include <iostream>
struct Handle {
    void* ptr;
};

struct Resource {
    int data = 42;
    Handle operator&() {
        std::cout << "Returning handle instead of raw address\n";
        return Handle{this};
    }
};

int main() {
    Resource r;
    auto h = &r;
    std::cout << "Handle points to: " << h.ptr << "\n";
}

Now something unexpected happens.

Instead of returning the actual memory address of r, the & operator is overloaded to return a custom Handle object.

So the question becomes:
How do we still get the real address of the object?

This is exactly what std::addressof is designed for.
When the & operator is overloaded, we can no longer rely on it to give the true address. Instead, std::addressof bypasses the overloaded operator and retrieves the actual underlying memory address.


But how does std::addressof works ?

A simplified version looks like this:

template<class T>
T* addressof(T& arg) noexcept
{
    return reinterpret_cast<T*>(
        &const_cast<char&>(
            reinterpret_cast<const volatile char&>(arg)
        )
    );
}

It looks scary at first, but once you see what it’s doing, it’s actually a very deliberate trick.
It does 3 things:

  1. View the object as raw bytes
    The object is first reinterpreted as a sequence of bytes:

    reinterpret_cast<const volatile char&>(arg)
    

    We use char because it allows safe access to the raw memory representation of any object since it is one byte in size.
    This is a common technique in low level C++ implementations like libc++. However, in modern C++, std::byte is often preferred because it makes the intent clearer and improves readability.

  2. Remove const volatile.

    const_cast<char&>(...)
    

    Why?
    This step removes const and volatile qualifiers so we can take a clean address of the underlying byte representation.

  3. Take the actual address.

    &const_cast<char&>(...)
    

    Now we can safely take the address because char does not have an overloaded operator&.

  4. Cast back to the original type

    reinterpret_cast<T*>(...)
    

    Finally, we convert the pointer from char* back to the original type T*.

Final Thoughts

This is a great example of how C++ gives you both:

  • high level abstractions (like operator overloading)

  • and low level control (like raw memory access)

std::addressof is just a safe way to get the real memory address, even if operator& has been overloaded.