LESSON 18 · INTERMEDIATE4 min read

Indexing and Indirection: operator[], operator->, and operator*

Implement container indexing and pointer wrapper abstractions: const and non-const operator[], smart pointer indirection (-> and *), and proxy objects.

C++20 / C++23Free to readModule 5 of 11
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Implement const and non-const overloads of operator[] for zero-overhead array indexing
  • Overload operator-> and operator* to construct transparent smart pointer and proxy wrappers
  • Explain the recursive drill-down behavior of the member access operator (->)

01Subscript Operator (operator[]) and Const Overloading

When designing custom collections, array wrappers, or matrix buffers, providing intuitive array indexing requires overloading the subscript operator: operator[].

A properly designed container must provide TWO overloads of operator[]:

1. Non-const overload: T& operator[](size_t index);. Returns a mutable reference to the element, permitting assignments like buffer[0] = 42;.

2. Const overload: const T& operator[](size_t index) const;. Returns a read-only reference when the container is accessed through a const reference or const object instance.

Omitting the const overload makes the container unusable inside read-only functions (e.g., void inspect(const MyVector& v)), while omitting the non-const overload prevents element mutation.

example.js⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Smart Pointer Indirection: operator* and operator->

Smart pointers (like std::unique_ptr or custom memory wrappers) must mimic the syntax of raw pointers. This requires overloading the dereference operator (*) and the member access arrow operator (->).

The dereference operator returns a reference to the pointed-to object: T& operator*() const noexcept { return *ptr_; }.

The arrow operator returns a raw pointer to the object: T* operator->() const noexcept { return ptr_; }.

03The Recursive Drill-Down Behavior of operator->

The arrow operator (->) possesses a unique syntactic property in the C++ language grammar: it applies recursively.

When the compiler parses sp->method(), it checks what sp.operator->() returns. If it returns a raw pointer (T*), the compiler accesses the member: (sp.operator->())->method().

However, if operator-> returns ANOTHER object that also overloads operator->, the compiler continues drilling down recursively until it finally reaches a raw pointer! This unique behavior enables proxy patterns, thread-safe synchronized wrappers, and lazy-loading ORM objects.

04Indirection Operators Summary

Summary of operator signatures for container access and smart pointer indirection:

Providing const correctness across both indexing and dereferencing is essential for production API design.

notes.txtPlain text
+----------------------+--------------------------+-----------------------+| Operator             | Signature                | Typical Role          |+----------------------+--------------------------+-----------------------+| operator[] (mutable) | T& operator[](size_t)    | Array element write   || operator[] (const)   | const T& operator[](sz)  | Array element read    || operator*            | T& operator*() const     | Object dereferencing  || operator->           | T* operator->() const    | Member access proxy   |+----------------------+--------------------------+-----------------------+

05Production Systems Architecture & Hardware Trade-Offs

From an operating system and hardware architecture perspective, systems software must account for physical execution realities that compiler abstractions intentionally hide. At the hardware level, instruction caches (I-Cache) and data caches (D-Cache) operate independently, and CPU pipeline front-ends rely heavily on branch target buffers (BTB) to minimize instruction fetch stalls. When code is organized into coherent, spatially contiguous blocks within translation units, binary code density increases, improving instruction cache hit rates across high-frequency execution loops.

Furthermore, modern linkers such as LLVM lld and mold perform Link-Time Optimization (LTO) across translation units. When LTO is enabled (-flto), the compiler writes LLVM bitcode into object files rather than final machine code, allowing the linker to perform cross-translation-unit function inlining, devirtualization, and dead-code stripping across the entire binary.

LET IT CLICK

The ideas worth keeping.

  • Containers must provide both const and non-const overloads of operator[] for read and write access.
  • Smart pointers implement operator* and operator-> to mimic raw pointer indirection.
  • operator-> drills down recursively until it reaches a raw pointer.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Exercise 1: Implement a minimal SmartPointer wrapper clas

Implement a minimal SmartPointer wrapper class that stores a raw pointer T* and provides both operator* and operator->.

your_turn_1.cpp⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.cppC++
template <typename T>class SmartPointer {private:    T* ptr_;public:    explicit SmartPointer(T* p) : ptr_(p) {}    ~SmartPointer() noexcept { delete ptr_; }    T& operator*() const noexcept { return *ptr_; }    T* operator->() const noexcept { return ptr_; }};

operator* returns T& by dereferencing ptr_; operator-> returns the raw pointer T* directly.

02

Exercise 2: Explain why standard containers like std vec

Explain why standard containers like std::vector provide both operator[] (unchecked) and at() (bounds-checked).

your_turn_2.cpp⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.cppC++
// operator[] is designed for maximum raw speed: it performs zero bounds checking,// compiling to a direct memory offset instruction in assembly (zero overhead).// at() provides defensive safety: it validates the index against size() and throws// std::out_of_range if invalid, incurring branching overhead.

operator[] optimizes for speed; at() optimizes for safety by throwing exceptions.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01What must operator-> return in an overloaded class?
02Why must a container class implement both const and non-const overloads of operator[]?
03What is the return type of operator* in a smart pointer managing type T?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Exercise 1: Implement a minimal SmartPointer wrapper clas

Implement a minimal SmartPointer wrapper class that stores a raw pointer T* and provides both operator* and operator->.

EXPECTED OUTPUT
Build completed. Binary executed.
[Exit code: 0]
main.cppC++
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.