Skip to content
C++
Language
since C++98
Advanced

Address-Of Overload

Overloading unary operator& on class types, its hazards in generic code, and how std::addressof bypasses any overload to retrieve the true address.

Address-Of Overloadsince C++98

Unary operator& is an overloadable operator that, when defined on a class type, intercepts the address-of expression &obj and returns an arbitrary value in place of the object's actual memory address.

Overview

Almost every piece of C++ code assumes &obj yields a pointer to obj's storage. Overloading operator& violates that assumption: the expression still compiles, but it now invokes user-defined code and may return any type β€” including a type that is not a pointer at all.

This makes operator& one of the most hazardous overloadable operators. Generic infrastructure β€” containers, allocators, placement new, std::construct_at β€” must not assume that &obj yields the real address. The C++11 standard library response was std::addressof, which always retrieves the actual storage address regardless of any overload.

Understanding the topic falls into two practical areas:

  • Authoring generic code: templates that touch object addresses must call std::addressof unconditionally, since they can be instantiated with a type that overloads operator&.
  • Recognising and auditing legacy types: COM-style smart pointers and some proxy types overload operator& to support out-parameter APIs, creating subtle hazards for callers.

Syntax

cpp
struct MyType {
    SomePointerType operator&();        // non-const overload
    ConstPointerType operator&() const; // const overload
};

The overload may be a member function (most common) or a non-member operator&(MyType&). The return type has no constraint from the language β€” returning an integer or a struct is syntactically valid, though semantically broken in almost all conceivable designs.

Examples

COM-style out-parameter pattern

The historically common motivation for overloading operator& was COM interfaces, where output-parameter APIs require a T**:

cpp
#include <iostream>
#include <memory>

template<class T>
class ComPtr {
    T* raw_ = nullptr;
public:
    explicit ComPtr(T* p = nullptr) : raw_(p) {}
    ~ComPtr() { delete raw_; }

    T*  get() const  { return raw_; }
    T** operator&()  { return &raw_; }   // yields T** for QueryInterface-style APIs
};

void create_object(int** pp) { *pp = new int(99); }

int main() {
    ComPtr<int> p;
    create_object(&p);           // operator& called; passes raw_'s address
    std::cout << *p.get();       // 99
}

The overload works in isolation, but breaks the moment a generic utility calls &p expecting a ComPtr<int>*.

std::addressof bypasses the overload

std::addressof<T> (C++11, <memory>) always returns the object's true address. It avoids involving operator& entirely by reinterpreting the object through char pointers at the implementation level. Compiler intrinsics power the real library implementations:

cpp
#include <iostream>
#include <memory>

template<class T>
struct Wrapper {
    T value;
    T** operator&() = delete;   // address-of is unusable via &
};

int main() {
    Wrapper<double> w{3.14};

    // &w;                          // error: deleted operator&
    Wrapper<double>* p = std::addressof(w);  // always works
    std::cout << p->value << '\n';  // 3.14
}

Since C++17, std::addressof is constexpr for object types (it was not constexpr in C++11/14).

Generic containers require std::addressof

Any template that constructs, destroys, or relocates objects in raw storage must use std::addressof. This includes custom allocators, storage wrappers, and type-erased containers:

cpp
#include <memory>
#include <type_traits>
#include <utility>

template<class T>
class ManualStorage {
    alignas(T) unsigned char buf_[sizeof(T)];
    bool live_ = false;
public:
    template<class... Args>
    void construct(Args&&... args) {
        ::new (std::addressof(*reinterpret_cast<T*>(buf_)))  // not &obj
            T(std::forward<Args>(args)...);
        live_ = true;
    }

    T& get() { return *reinterpret_cast<T*>(buf_); }

    void destroy() {
        if (live_) {
            std::addressof(get())->~T();   // not get().~T()
            live_ = false;
        }
    }
};

Using &obj instead of std::addressof(obj) here silently misbehaves when T overloads operator&, passing the wrong address to placement new or the destructor.

Self-assignment guards

The canonical self-assignment check in copy/move assignment relies on address comparison. When T overloads operator&, this != &other may compare incompatible types or call the overload with no meaningful result. The correct form uses std::addressof:

cpp
MyType& operator=(const MyType& other) {
    if (this != std::addressof(other)) {
        // perform copy
    }
    return *this;
}

The standard library's own implementations, such as std::optional::operator= in libc++, follow this pattern.

The ADL ill-formed edge case

There is a subtle scenario where the built-in & is ill-formed via argument-dependent lookup β€” even without any overload:

cpp
template<class T>
struct Holder { T t; };

struct Incomplete;  // declared but not defined

int main() {
    Holder<Holder<Incomplete>*> x{};
    // &x;               // error: ADL instantiates Holder<Incomplete>, which is incomplete
    std::addressof(x);   // OK β€” no ADL involved
}

This corner case is rare in practice but confirms that std::addressof is the unconditionally safe choice in template code.

Best Practices

Use std::addressof unconditionally in templates. Any template parameter type could, in principle, overload operator&. Calling std::addressof costs nothing at runtime and eliminates the entire class of bugs.

Do not overload operator& on new types. Modern C++ provides better alternatives for every historical use-case:

  • COM out-parameters: use a named put() or reset_and_get_address() method.
  • Pointer wrappers: use std::pointer_traits customization.
  • Checked/instrumented pointers: expose the inner pointer through get().

Use std::to_address (C++20) for pointer-like types. If you are implementing a fancy pointer (custom iterator, allocator pointer), the correct extension point is std::pointer_traits<Ptr>::to_address or the ADL-friendly std::to_address:

cpp
// C++20 β€” works for raw pointers and fancy pointers alike
template<class Ptr>
auto as_raw(Ptr& p) {
    return static_cast<void*>(std::to_address(p));
}

std::to_address participates in std::allocator_traits and is the mechanism by which C++20 containers handle fancy pointers correctly.

Common Pitfalls

Returning non-pointer types. The compiler accepts int operator&() const { return 0; }. Code that calls &obj expecting a pointer will fail in bewildering ways: wrong overload resolution, implicit conversions, or outright compile errors deep inside a call chain.

Broken placement new. Passing the result of an overloaded & to placement new produces undefined behaviour if the returned value is not the object's actual address. This is difficult to diagnose because the bug only manifests at runtime, often as a write to unrelated memory.

constexpr limitations before C++17. std::addressof became constexpr for object types in C++17. Code that uses it in constexpr contexts and targets C++11/14 will fail to compile. There is no portable workaround short of compiler builtins (__builtin_addressof in GCC/Clang).

Interaction with [[no_unique_address]] (C++20). When a member is annotated [[no_unique_address]], its address relative to the containing object may be the same as another member. Code that relies on operator& for such members to provide distinguishing identity gets undefined results.

See Also

  • std::addressof β€” <memory>, C++11; constexpr since C++17
  • std::to_address β€” <memory>, C++20; retrieves a raw pointer from any pointer-like type
  • std::pointer_traits β€” <memory>, C++11; customisation point for allocator-aware pointer types
  • std::allocator_traits::construct β€” mandated to use std::addressof internally since C++11