Page Content

Tutorials

What is Overloadable And Non-Overloadable Operators In C++?

Overloadable and Non-Overloadable Operators in C++

Overloadable Operators

An operator function is an overloaded operator. When you declare an operator function, the operator is preceded by the keyword operator. Similar to overloaded functions, overloaded operators can be identified by the types and quantity of operands they use on the operator.

Overloading most C++ operators is possible:

  • Arithmetic Operators: +, -, *, /, %.
  • Relational Operators: ==,!=, <, <=, >, >=.
  • Logical Operators: !, &&, and ||.
  • Bitwise Operators: &, |, ^, ~, <<, >>.
  • Assignment Operators: =, +=, -=, *=, /=, %=, ^=, &=, |=, <<=, and >>=.
  • Increment and Decrement Operators: ++ and — .
  • Special Operators:
    • Function Call Operator (): Function objects, or functors, are handled via the function call operator ().
    • Array Subscript Operator []: Class classes that exhibit array-like behaviour are addressed via the array subscript operator [].
    • Member Access Operators ->, ->*: Iterators and smart pointers are the main applications for Member Access Operators ->, ->*.
    • Comma Operator: ,.
    • Memory Management Operators: Memory management operators include delete, delete[], new, and new[].
    • Type Conversion Operators: Operator type() is a type conversion operator that can be used explicitly or implicitly.

Code Example:

For a user-defined USCurrency struct, the following code sample shows how to overload the addition operator (+) and the stream insertion operator (<<):

#include <iostream> // For cout and endl
// Define a user-defined datatype USCurrency 
struct USCurrency {
    int dollars; // Member to store dollar amount 
    int cents;   // Member to store cent amount 
};
// Overload the + operator to add two USCurrency objects 
// This is defined as a global function, independent of the class 
USCurrency operator+(const USCurrency m, const USCurrency o) {
    USCurrency tmp = {0, 0}; // Initialize a temporary USCurrency object 
    tmp.cents = m.cents + o.cents; // Add cents 
    tmp.dollars = m.dollars + o.dollars; // Add dollars 
    // Handle overflow if cents exceed 100
    if (tmp.cents >= 100) {
        tmp.dollars += 1;   // Add 1 to dollars if cents are 100 or more
        tmp.cents -= 100;   // Subtract 100 from cents 
    }
    return tmp; // Return the new USCurrency object 
}
// Overload the << operator to display USCurrency objects 
// This is typically defined as a global friend function for IO operators 
std::ostream& operator<<(std::ostream &output, const USCurrency &o) {
    output << "$" << o.dollars << "." << o.cents; // Format the output as "$dollars.cents" 
    return output; // Return the ostream reference to allow chaining [26, 30
}
int main() {
    USCurrency a = {2, 50}; // US$2.50
    USCurrency b = {1, 75}; // US$1.75 
    USCurrency c = a + b;   // Add 'a' and 'b' using the overloaded '+' operator 
    std::cout << c << std::endl; // Print 'c' using the overloaded '<<' operator
    return 0; // End of program 
}

Output

$4.25

Non-Overloadable Operators

Overloading some operators is impossible those are known as Non-Overloadable Operators. Generally speaking, these are operations that have basic, unchangeable meanings associated with the C++ language, or their overloading might result in ambiguities or strange behaviour:

  • Scope Resolution Operator: ::.
  • Member Selection Operators: . (dot operator) and.* (pointer to member dot operator) are Member Selection Operators. Usually, they are used to directly access names and do not accept values as operands.
  • Conditional Operator: ?:.
  • sizeof Operator: The sizeof operator provides the size of a type or object at compilation time.
  • alignof Operator: Indicates how an object or type is aligned.
  • typeid Operator: For run-time type identification, use the typeid operator.
  • Preprocessor symbols: The symbols # and ## are preprocessors.

Code example:

#include <iostream>
int main() {
    int a = 10;
    int* ptr = &a;
    // The sizeof operator cannot be overloaded.
    std::cout << "Size of a: " << sizeof(a) << std::endl;
    std::cout << "Size of int: " << sizeof(int) << std::endl;
    // The :: (scope resolution) operator cannot be overloaded.
    // Example: std::cout uses the scope resolution operator.
    std::cout << "Using scope resolution operator for std::cout" << std::endl;
    // The . (member access) operator cannot be overloaded.
    // We can't demonstrate overloading for primitive types, but it applies
    // to class member access as well.
    // The .* (pointer to member) operator cannot be overloaded.
    // This is more complex and involves pointers to members of classes.
    // The ? : (ternary conditional) operator cannot be overloaded.
    int b = (a > 5) ? 20 : 5;
    std::cout << "Ternary result: " << b << std::endl;
    // The typeid operator cannot be overloaded.
    std::cout << "Type of a: " << typeid(a).name() << std::endl;
    // The static_cast, dynamic_cast, reinterpret_cast, const_cast operators
    // cannot be overloaded. These are compile-time casting operators.
    double d = static_cast<double>(a);
    std::cout << "Static cast result: " << d << std::endl;
    return 0;
}

Output

Size of a: 4
Size of int: 4
Using scope resolution operator for std::cout
Ternary result: 20
Type of a: i
Static cast result: 10

You can also read What Are The Types Of Operator Overloading In C++?

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index