C++ Operator Overloading and Non-Overloadable Operators

This article provides an overview of operator overloading in C++, explaining how it allows developers to redefine how standard operators interact with user-defined types. It covers the core concept and purpose of operator overloading, followed by an explicit breakdown of the specific C++ operators that the language standard strictly prohibits from being overloaded, along with the technical reasons behind these restrictions.

What Is Operator Overloading?

Operator overloading is a compile-time polymorphism feature in C++ that allows standard operators (such as +, -, *, ==, and <<) to have user-defined meanings when applied to custom data types like classes and structs.

In standard C++, operators are predefined for primitive types (e.g., adding two integers using a + b). Through operator overloading, you can define a special member function or a non-member friend function using the operator keyword so that your custom objects can be manipulated using the same intuitive syntax. For example, rather than calling a method like vector1.add(vector2), you can write vector1 + vector2.

Operators That Cannot Be Overloaded

While C++ allows the majority of its operators to be overloaded, a small set cannot be modified. The C++ standard explicitly forbids overloading the following operators:

1. Scope Resolution Operator (::)

The scope resolution operator is resolved entirely at compile time to specify namespace, class, or enumeration scopes. Overloading it would create severe syntactic ambiguity, making it impossible for the compiler to distinguish between a namespace lookup and an overloaded operation.

2. Member Access Operator (.)

The direct member access (dot) operator is fundamental to accessing members of an object. Allowing this operator to be overloaded would compromise member lookup logic and obscure how class members are reached. Note that while . cannot be overloaded, the pointer-to-member arrow operator (->) can be.

3. Member Pointer Selector Operator (.*)

Similar to the dot operator, the pointer-to-member dereference operator (.*) is bound to the core mechanics of accessing member functions or variables via pointers within an instance. Overloading it would disrupt object-model guarantees. The indirect version (->*), however, can be overloaded.

4. Ternary Conditional Operator (?:)

The ternary conditional operator evaluates an expression based on a condition: condition ? true_expr : false_expr. Crucially, it guarantees short-circuit evaluation—only one of the two expressions is evaluated. In C++, overloaded operators act as function calls where all arguments must be evaluated before the function executes. Overloading ?: would break this short-circuiting guarantee.

5. Object Size Operator (sizeof)

The sizeof operator returns the memory size of a type or object in bytes. It is evaluated at compile time and is vital for low-level memory allocation, array sizing, and type layout. Modifying its behavior would fundamentally break memory management.

6. Type Identification Operator (typeid)

Part of Run-Time Type Information (RTTI), typeid queries the runtime type of an expression. Because the integrity of the type system relies on typeid providing accurate metadata, C++ prohibits user-defined overrides.

7. Alignment Operator (alignof)

Introduced in modern C++, alignof queries the alignment requirement of a type at compile time. Like sizeof, it is an intrinsic compiler query that cannot be altered.

8. Casting Operators

Explicit casting operators—such as static_cast, dynamic_cast, const_cast, and reinterpret_cast—are intrinsic type-conversion keywords rather than traditional overloadable operators.