C++ Operator Overloading Guidelines

One of the nice features of C++ is that you can give special meanings to operators, when they are used with user-defined classes. This is called operator overloading . You can implement C++ operator overloads by providing special member-functions on your classes that follow a particular naming convention. For example, to overload the + operator for your class, you would provide a member-function named operator+ on your class.

  • = (assignment operator)
  • + - * (binary arithmetic operators)
  • += -= *= (compound assignment operators)
  • == != (comparison operators)

Here are some guidelines for implementing these operators. These guidelines are very important to follow, so definitely get in the habit early.

Assignment Operator =

The assignment operator has a signature like this: class MyClass { public: ... MyClass & operator=(const MyClass &rhs); ... } MyClass a, b; ... b = a; // Same as b.operator=(a);

Notice that the = operator takes a const-reference to the right hand side of the assignment. The reason for this should be obvious, since we don't want to change that value; we only want to change what's on the left hand side.

  • e = 42 assigns 42 to e , then returns e as the result
  • The value of e is then assigned to d , and then d is returned as the result
  • The value of d is then assigned to c , and then c is returned as the result

Now, in order to support operator chaining, the assignment operator must return some value. The value that should be returned is a reference to the left-hand side of the assignment.

Notice that the returned reference is not declared const . This can be a bit confusing, because it allows you to write crazy stuff like this: MyClass a, b, c; ... (a = b) = c; // What?? At first glance, you might want to prevent situations like this, by having operator= return a const reference. However, statements like this will work with primitive types. And, even worse, some tools actually rely on this behavior. Therefore, it is important to return a non- const reference from your operator= . The rule of thumb is, "If it's good enough for int s, it's good enough for user-defined data-types."

So, for the hypothetical MyClass assignment operator, you would do something like this: // Take a const-reference to the right-hand side of the assignment. // Return a non-const reference to the left-hand side. MyClass& MyClass::operator=(const MyClass &rhs) { ... // Do the assignment operation! return *this; // Return a reference to myself. } Remember, this is a pointer to the object that the member function is being called on. Since a = b is treated as a.operator=(b) , you can see why it makes sense to return the object that the function is called on; object a is the left-hand side.

But, the member function needs to return a reference to the object, not a pointer to the object. So, it returns *this , which returns what this points at (i.e. the object), not the pointer itself. (In C++, instances are turned into references, and vice versa, pretty much automatically, so even though *this is an instance, C++ implicitly converts it into a reference to the instance.)

Now, one more very important point about the assignment operator:

YOU MUST CHECK FOR SELF-ASSIGNMENT!

This is especially important when your class does its own memory allocation. Here is why: The typical sequence of operations within an assignment operator is usually something like this: MyClass& MyClass::operator=(const MyClass &rhs) { // 1. Deallocate any memory that MyClass is using internally // 2. Allocate some memory to hold the contents of rhs // 3. Copy the values from rhs into this instance // 4. Return *this } Now, what happens when you do something like this: MyClass mc; ... mc = mc; // BLAMMO. You can hopefully see that this would wreak havoc on your program. Because mc is on the left-hand side and on the right-hand side, the first thing that happens is that mc releases any memory it holds internally. But, this is where the values were going to be copied from, since mc is also on the right-hand side! So, you can see that this completely messes up the rest of the assignment operator's internals.

The easy way to avoid this is to CHECK FOR SELF-ASSIGNMENT. There are many ways to answer the question, "Are these two instances the same?" But, for our purposes, just compare the two objects' addresses. If they are the same, then don't do assignment. If they are different, then do the assignment.

So, the correct and safe version of the MyClass assignment operator would be this: MyClass& MyClass::operator=(const MyClass &rhs) { // Check for self-assignment! if (this == &rhs) // Same object? return *this; // Yes, so skip assignment, and just return *this. ... // Deallocate, allocate new space, copy values... return *this; } Or, you can simplify this a bit by doing: MyClass& MyClass::operator=(const MyClass &rhs) { // Only do assignment if RHS is a different object from this. if (this != &rhs) { ... // Deallocate, allocate new space, copy values... } return *this; } Remember that in the comparison, this is a pointer to the object being called, and &rhs is a pointer to the object being passed in as the argument. So, you can see that we avoid the dangers of self-assignment with this check.

  • Take a const-reference for the argument (the right-hand side of the assignment).
  • Return a reference to the left-hand side, to support safe and reasonable operator chaining. (Do this by returning *this .)
  • Check for self-assignment, by comparing the pointers ( this to &rhs ).

Compound Assignment Operators += -= *=

I discuss these before the arithmetic operators for a very specific reason, but we will get to that in a moment. The important point is that these are destructive operators, because they update or replace the values on the left-hand side of the assignment. So, you write: MyClass a, b; ... a += b; // Same as a.operator+=(b) In this case, the values within a are modified by the += operator.

How those values are modified isn't very important - obviously, what MyClass represents will dictate what these operators mean.

The member function signature for such an operator should be like this: MyClass & MyClass::operator+=(const MyClass &rhs) { ... } We have already covered the reason why rhs is a const-reference. And, the implementation of such an operation should also be straightforward.

But, you will notice that the operator returns a MyClass -reference, and a non-const one at that. This is so you can do things like this: MyClass mc; ... (mc += 5) += 3;

Don't ask me why somebody would want to do this, but just like the normal assignment operator, this is allowed by the primitive data types. Our user-defined datatypes should match the same general characteristics of the primitive data types when it comes to operators, to make sure that everything works as expected.

This is very straightforward to do. Just write your compound assignment operator implementation, and return *this at the end, just like for the regular assignment operator. So, you would end up with something like this: MyClass & MyClass::operator+=(const MyClass &rhs) { ... // Do the compound assignment work. return *this; }

As one last note, in general you should beware of self-assignment with compound assignment operators as well. Fortunately, none of the C++ track's labs require you to worry about this, but you should always give it some thought when you are working on your own classes.

Binary Arithmetic Operators + - *

The binary arithmetic operators are interesting because they don't modify either operand - they actually return a new value from the two arguments. You might think this is going to be an annoying bit of extra work, but here is the secret:

Define your binary arithmetic operators using your compound assignment operators.

There, I just saved you a bunch of time on your homeworks.

So, you have implemented your += operator, and now you want to implement the + operator. The function signature should be like this: // Add this instance's value to other, and return a new instance // with the result. const MyClass MyClass::operator+(const MyClass &other) const { MyClass result = *this; // Make a copy of myself. Same as MyClass result(*this); result += other; // Use += to add other to the copy. return result; // All done! } Simple!

Actually, this explicitly spells out all of the steps, and if you want, you can combine them all into a single statement, like so: // Add this instance's value to other, and return a new instance // with the result. const MyClass MyClass::operator+(const MyClass &other) const { return MyClass(*this) += other; } This creates an unnamed instance of MyClass , which is a copy of *this . Then, the += operator is called on the temporary value, and then returns it.

If that last statement doesn't make sense to you yet, then stick with the other way, which spells out all of the steps. But, if you understand exactly what is going on, then you can use that approach.

You will notice that the + operator returns a const instance, not a const reference. This is so that people can't write strange statements like this: MyClass a, b, c; ... (a + b) = c; // Wuh...? This statement would basically do nothing, but if the + operator returns a non- const value, it will compile! So, we want to return a const instance, so that such madness will not even be allowed to compile.

  • Implement the compound assignment operators from scratch, and then define the binary arithmetic operators in terms of the corresponding compound assignment operators.
  • Return a const instance, to prevent worthless and confusing assignment operations that shouldn't be allowed.

Comparison Operators == and !=

The comparison operators are very simple. Define == first, using a function signature like this: bool MyClass::operator==(const MyClass &other) const { ... // Compare the values, and return a bool result. } The internals are very obvious and straightforward, and the bool return-value is also very obvious.

The important point here is that the != operator can also be defined in terms of the == operator, and you should do this to save effort. You can do something like this: bool MyClass::operator!=(const MyClass &other) const { return !(*this == other); } That way you get to reuse the hard work you did on implementing your == operator. Also, your code is far less likely to exhibit inconsistencies between == and != , since one is implemented in terms of the other.

  • 90% Refund @Courses
  • C++ Data Types
  • C++ Input/Output
  • C++ Pointers
  • C++ Interview Questions
  • C++ Programs
  • C++ Cheatsheet
  • C++ Projects
  • C++ Exception Handling
  • C++ Memory Management

Related Articles

  • Solve Coding Problems
  • C++ Ternary or Conditional Operator
  • C++ Logical Operators
  • C++ Relational Operators
  • Increment (++) and Decrement (--) Operator Overloading in C++
  • Types of Operator Overloading in C++
  • Typecast Operator Overloading in C++
  • Casting Operators in C++
  • How to Lock Window Resize C++ sfml?
  • How to Overload == Operator in C++?
  • C++ Arithmetic Operators
  • dot (.) operator in C++
  • Increment Operator Behavior When Passed as Function Parameters in C++
  • Overloading of function-call operator in C++
  • Constants in C++
  • C++ Program For Iterative Quick Sort
  • C++ sizeof Operator
  • C++ Increment and Decrement Operators
  • C++ Comparison Operators
  • Bitwise Operators in C++

C++ Assignment Operator Overloading

Prerequisite: Operator Overloading

The assignment operator,”=”, is the operator used for Assignment. It copies the right value into the left value. Assignment Operators are predefined to operate only on built-in Data types.

  • Assignment operator overloading is binary operator overloading.
  • Overloading assignment operator in C++ copies all values of one object to another object.
  • Only a non-static member function should be used to overload the assignment operator.

We can’t directly use the Assignment Operator on objects. The simple explanation for this is that the Assignment Operator is predefined to operate only on built-in Data types. As the class and objects are user-defined data types, so the compiler generates an error.

here, a and b are of type integer, which is a built-in data type. Assignment Operator can be used directly on built-in data types.

c1 and c2 are variables of type “class C”. Here compiler will generate an error as we are trying to use an Assignment Operator on user-defined data types.

The above example can be done by implementing methods or functions inside the class, but we choose operator overloading instead. The reason for this is, operator overloading gives the functionality to use the operator directly which makes code easy to understand, and even code size decreases because of it. Also, operator overloading does not affect the normal working of the operator but provides extra functionality to it.

Now, if the user wants to use the assignment operator “=” to assign the value of the class variable to another class variable then the user has to redefine the meaning of the assignment operator “=”.  Redefining the meaning of operators really does not change their original meaning, instead, they have been given additional meaning along with their existing ones.

Whether you're preparing for your first job interview or aiming to upskill in this ever-evolving tech landscape, GeeksforGeeks Courses are your key to success. We provide top-quality content at affordable prices, all geared towards accelerating your growth in a time-bound manner. Join the millions we've already empowered, and we're here to do the same for you. Don't miss out - check it out now !

Please Login to comment...

  • cpp-operator
  • cpp-operator-overloading
  • Apple's New AI-powered Tool: Editing Through Text Prompts
  • Rebranding Google Bard to Gemini: All You Need to Know, Android App and Advanced subscriptions
  • Youtube TV's Multiview Update: Tailor Your Experience in 4 Easy Steps
  • Kore.ai Secures $150 Million for AI-Powered Growth
  • 10 Best IPTV Service Provider Subscriptions

Improve your Coding Skills with Practice

 alt=

What kind of Experience do you want to share?

How to Implement Assignment Operator Overloading in C++

  • How to Implement Assignment Operator …

How to Implement Assignment Operator Overloading in C++

This article will explain several methods of how to implement assignment operator overloading in C++.

Use copy-assignment operator to Implement Overloaded Assignment Operator in C++

C++ provides the feature to overload operators, a common way to call custom functions when a built-in operator is called on specific classes. These functions should have a special name starting with operator followed by the specific operator symbol itself. E.g., a custom assignment operator can be implemented with the function named operator= . Assignment operator generally should return a reference to its left-hand operand. Note that if the user does not explicitly define the copy assignment operator, the compiler generates one automatically. The generated version is quite capable when the class does not contain any data members manually allocated on the heap memory. It can even handle the array members by assigning each element to the corresponding object members. Although, it has shortcomings when dealing with dynamic memory data members, as shown in the following example code.

The above code defines only copy-constructor explicitly, which results in incorrect behavior when P1 object contents are assigned to the P3 object. Note that the second call to the P1.renamePerson function should not have modified the P3 object’s data members, but it did. The solution to this is to define an overloaded assignment operator i.e., copy-assignment operator. The next code snippet implements the version of the Person class that can copy assign the two objects of the same class correctly. Notice, though, the if statement in the copy-assignment function guarantees that the operator works correctly even when the object is assigned to itself.

Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

Related Article - C++ Class

  • How to Initialize Static Variables in C++ Class
  • How to Get Class Name in C++
  • Point and Line Class in C++
  • Class Template Inheritance in C++
  • Difference Between Structure and Class in C++
  • Wrapper Class in C++

C++ overloading: operators to overload as methods of a class

overloaded assignment operator return reference

Get the Learn to Code Starter Pack

Break into tech with the logic & computer science skills you’d learn in a bootcamp or university — at a fraction of the cost. Educative's hand-on curriculum is perfect for new learners hoping to launch a career.

Operator overloading is an interesting topic of object-oriented programming using C++. Operator overloading enables the objects of a class to be used in expressions. Some operators must be overloaded as non-static member functions of the class. This blog discusses these operators with coding examples.

Let’s dive right in!

We’ll cover:

Introduction

  • Assignment operators

Single overload

Two ways to overload, noninteger index, function call operator, operators that are preferred as members, wrapping up and next steps.

Operators are overloaded as functions in C++. They can be overloaded in various ways: as member functions only, as nonmembers only, and as both members and nonmembers. Here, we discuss the operators that can be overloaded as non-static member functions only. The first operand or the caller of these operators is always an object of the class. Other aspects related to operator overloading are discussed in Operator Overloading in C++ and Various Ways of Overloading an Operator in C++ .

The operators that can be overloaded in C++. The green subset must be overloaded as methods.

From this point onward, we’ll keep using and extending this minimal implementation of the Time class.

Lines 6–29: We define a Time class as described before the code. Additionally, it defines the subscript operator ( lines 21–28 ).

  • Line 21: This receives a string parameter to mention the specific attribute that we want to access for read/write. It accepts only "hour" , "minute" , or "second" as an argument. It throws an exception if it receives any other string.

Lines 31–43: We define the main() function that creates an object noon of type Time .

Line 36: We call the subscript operator to read the value of "hour" from noon .

Line 39: We call the subscript operator to write the value of "second" to noon .

Zero to Hero in C++

Cover

Learn in-demand tech skills in half the time

Skill Paths

Assessments

Learn to Code

Tech Interview Prep

Generative AI

Data Science

Machine Learning

GitHub Students Scholarship

Early Access Courses

For Individuals

Try for Free

Become an Author

Become an Affiliate

Earn Referral Credits

Frequently Asked Questions

Privacy Policy

Cookie Policy

Terms of Service

Business Terms of Service

Data Processing Agreement

Copyright © 2024 Educative, Inc. All rights reserved.

  • Windows Programming
  • UNIX/Linux Programming
  • General C++ Programming
  • Operator Overloading : Return by Referen

    Operator Overloading : Return by Reference

overloaded assignment operator return reference

cppreference.com

Search

operator overloading

Customizes the C++ operators for operands of user-defined types.

[ edit ] Syntax

Overloaded operators are functions with special function names:

[ edit ] Overloaded operators

When an operator appears in an expression , and at least one of its operands has a class type or an enumeration type , then overload resolution is used to determine the user-defined function to be called among all the functions whose signatures match the following:

Note: for overloading user-defined conversion functions , user-defined literals , allocation and deallocation see their respective articles.

Overloaded operators (but not the built-in operators) can be called using function notation:

[ edit ] Restrictions

  • The operators :: (scope resolution), . (member access), .* (member access through pointer to member), and ?: (ternary conditional) cannot be overloaded
  • New operators such as ** , <> , or &| cannot be created
  • The overloads of operators && , || , and , (comma) lose their special properties: short-circuit evaluation and sequencing .
  • The overload of operator -> must either return a raw pointer or return an object (by reference or by value), for which operator -> is in turn overloaded.
  • It is not possible to change the precedence, grouping, or number of operands of operators.

[ edit ] Canonical implementations

Other than the restrictions above, the language puts no other constraints on what the overloaded operators do, or on the return type (it does not participate in overload resolution), but in general, overloaded operators are expected to behave as similar as possible to the built-in operators: operator + is expected to add, rather than multiply its arguments, operator = is expected to assign, etc. The related operators are expected to behave similarly ( operator + and operator + = do the same addition-like operation). The return types are limited by the expressions in which the operator is expected to be used: for example, assignment operators return by reference to make it possible to write a = b = c = d , because the built-in operators allow that. Commonly overloaded operators have the following typical, canonical forms: [1]

[ edit ] Assignment operator

The assignment operator ( operator = ) has special properties: see copy assignment and move_assignment for details. To summarize, the canonical "universal assignment operator" implementation is

When there are resources that can be reused in assignment, for example, if the class owns a heap-allocated array, then copy-assignment between arrays of the same size can avoid allocation and deallocation:

[ edit ] Stream extraction and insertion

The overloads of operator>> and operator<< that take a std:: istream & or std:: ostream & as the left hand argument are known as insertion and extraction operators. Since they take the user-defined type as the right argument ( b in a@b ), they must be implemented as non-members.

These operators are sometimes implemented as friend functions .

[ edit ] Function call operator

When a user-defined class overloads the function call operator, operator ( ) , it becomes a FunctionObject type. Many standard algorithms, from std:: sort to std:: accumulate accept objects of such types to customize behavior. There are no particularly notable canonical forms of operator ( ) , but to illustrate the usage

[ edit ] Increment and decrement

When the postfix increment and decrement appear in an expression, the corresponding user-defined function ( operator ++ or operator -- ) is called with an integer argument 0 . Typically, it is implemented as T operator ++ ( int ) , where the argument is ignored. The postfix increment and decrement operator is usually implemented in terms of the prefix version:

Although canonical form of pre-increment/pre-decrement returns a reference, as with any operator overload, the return type is user-defined; for example the overloads of these operators for std::atomic return by value.

[ edit ] Binary arithmetic operators

Binary operators are typically implemented as non-members to maintain symmetry (for example, when adding a complex number and an integer, if operator+ is a member function of the complex type, then only complex + integer would compile, and not integer + complex ). Since for every binary arithmetic operator there exists a corresponding compound assignment operator, canonical forms of binary operators are implemented in terms of their compound assignments:

[ edit ] Relational operators

Standard algorithms such as std:: sort and containers such as std:: set expect operator < to be defined, by default, for the user-provided types, and expect it to implement strict weak ordering (thus satisfying the Compare concept). An idiomatic way to implement strict weak ordering for a structure is to use lexicographical comparison provided by std::tie :

Typically, once operator < is provided, the other relational operators are implemented in terms of operator < .

Likewise, the inequality operator is typically implemented in terms of operator == :

[ edit ] Array subscript operator

User-defined classes that provide array-like access that allows both reading and writing typically define two overloads for operator [ ] : const and non-const variants:

If the value type is known to be a built-in type, the const variant should return by value.

Where direct access to the elements of the container is not wanted or not possible or distinguishing between l-value c [ i ] = v ; and r-value v = c [ i ] ; usage, operator[] may return a proxy. see for example std::bitset::operator[] .

To provide multidimensional array access semantics, e.g. to implement a 3D array access a [ i ] [ j ] [ k ] = x ; , operator[] has to return a reference to a 2D plane, which has to have its own operator[] which returns a reference to a 1D row, which has to have operator[] which returns a reference to the element. To avoid this complexity, some libraries opt for overloading operator ( ) instead, so that 3D access expressions have the Fortran-like syntax a ( i,j,k ) = x ;

[ edit ] Example

[ edit ] see also.

  • Operator precedence
  • Alternative operator syntax

[ edit ] References

  • ↑ Operator Overloading on StackOverflow C++ FAQ
  • Recent changes
  • Offline version
  • What links here
  • Related changes
  • Upload file
  • Special pages
  • Printable version
  • Permanent link
  • Page information
  • In other languages
  • This page was last modified on 14 August 2015, at 05:37.
  • This page has been accessed 742,100 times.
  • Privacy policy
  • About cppreference.com
  • Disclaimers

Powered by MediaWiki

operator overloading

Customizes the C++ operators for operands of user-defined types.

Overloaded operators are functions with special function names:

Overloaded operators

When an operator appears in an expression , and at least one of its operands has a class type or an enumeration type , then overload resolution is used to determine the user-defined function to be called among all the functions whose signatures match the following:

Note: for overloading user-defined conversion functions , user-defined literals , allocation and deallocation see their respective articles.

Overloaded operators (but not the built-in operators) can be called using function notation:

Restrictions

  • The operators :: (scope resolution), . (member access), .* (member access through pointer to member), and ?: (ternary conditional) cannot be overloaded.
  • New operators such as ** , <> , or &| cannot be created.
  • The overloads of operators && and || lose short-circuit evaluation.
  • The overload of operator -> must either return a raw pointer, or return an object (by reference or by value) for which operator -> is in turn overloaded.
  • It is not possible to change the precedence, grouping, or number of operands of operators.

Canonical implementations

Other than the restrictions above, the language puts no other constraints on what the overloaded operators do, or on the return type (it does not participate in overload resolution), but in general, overloaded operators are expected to behave as similar as possible to the built-in operators: operator + is expected to add, rather than multiply its arguments, operator = is expected to assign, etc. The related operators are expected to behave similarly ( operator + and operator + = do the same addition-like operation). The return types are limited by the expressions in which the operator is expected to be used: for example, assignment operators return by reference to make it possible to write a = b = c = d , because the built-in operators allow that.

Commonly overloaded operators have the following typical, canonical forms: [1]

Assignment operator

The assignment operator ( operator = ) has special properties: see copy assignment and move assignment for details.

The canonical copy-assignment operator is expected to perform no action on self-assignment , and to return the lhs by reference:

The canonical move assignment is expected to leave the moved-from object in valid state (that is, a state with class invariants intact), and either do nothing or at least leave the object in a valid state on self-assignment, and return the lhs by reference to non-const, and be noexcept:

In those situations where copy assignment cannot benefit from resource reuse (it does not manage a heap-allocated array and does not have a (possibly transitive) member that does, such as a member std::vector or std::string ), there is a popular convenient shorthand: the copy-and-swap assignment operator, which takes its parameter by value (thus working as both copy- and move-assignment depending on the value category of the argument), swaps with the parameter, and lets the destructor clean it up.

This form automatically provides strong exception guarantee , but prohibits resource reuse.

Stream extraction and insertion

The overloads of operator>> and operator<< that take a std:: istream & or std:: ostream & as the left hand argument are known as insertion and extraction operators. Since they take the user-defined type as the right argument ( b in a@b ), they must be implemented as non-members.

These operators are sometimes implemented as friend functions .

Function call operator

When a user-defined class overloads the function call operator, operator ( ) , it becomes a FunctionObject type. Many standard algorithms, from std:: sort to std:: accumulate accept objects of such types to customize behavior. There are no particularly notable canonical forms of operator ( ) , but to illustrate the usage

Increment and decrement

When the postfix increment and decrement appear in an expression, the corresponding user-defined function ( operator ++ or operator -- ) is called with an integer argument 0 . Typically, it is implemented as T operator ++ ( int ) , where the argument is ignored. The postfix increment and decrement operator is usually implemented in terms of the prefix version:

Although canonical form of pre-increment/pre-decrement returns a reference, as with any operator overload, the return type is user-defined; for example the overloads of these operators for std::atomic return by value.

Binary arithmetic operators

Binary operators are typically implemented as non-members to maintain symmetry (for example, when adding a complex number and an integer, if operator+ is a member function of the complex type, then only complex + integer would compile, and not integer + complex ). Since for every binary arithmetic operator there exists a corresponding compound assignment operator, canonical forms of binary operators are implemented in terms of their compound assignments:

Relational operators

Standard algorithms such as std:: sort and containers such as std:: set expect operator < to be defined, by default, for the user-provided types, and expect it to implement strict weak ordering (thus satisfying the Compare requirements). An idiomatic way to implement strict weak ordering for a structure is to use lexicographical comparison provided by std::tie :

Typically, once operator < is provided, the other relational operators are implemented in terms of operator < .

Likewise, the inequality operator is typically implemented in terms of operator == :

When three-way comparison (such as std::memcmp or std::string::compare ) is provided, all six relational operators may be expressed through that:

Array subscript operator

User-defined classes that provide array-like access that allows both reading and writing typically define two overloads for operator [ ] : const and non-const variants:

If the value type is known to be a built-in type, the const variant should return by value.

Where direct access to the elements of the container is not wanted or not possible or distinguishing between lvalue c [ i ] = v ; and rvalue v = c [ i ] ; usage, operator[] may return a proxy. see for example std::bitset::operator[] .

To provide multidimensional array access semantics, e.g. to implement a 3D array access a [ i ] [ j ] [ k ] = x ; , operator[] has to return a reference to a 2D plane, which has to have its own operator[] which returns a reference to a 1D row, which has to have operator[] which returns a reference to the element. To avoid this complexity, some libraries opt for overloading operator ( ) instead, so that 3D access expressions have the Fortran-like syntax a ( i, j, k ) = x ;

Bitwise arithmetic operators

User-defined classes and enumerations that implement the requirements of BitmaskType are required to overload the bitwise arithmetic operators operator & , operator | , operator ^ , operator~ , operator & = , operator | = , and operator ^ = , and may optionally overload the shift operators operator << operator >> , operator >>= , and operator <<= . The canonical implementations usually follow the pattern for binary arithmetic operators described above.

Boolean negation operator

The operator operator ! is commonly overloaded by the user-defined classes that are intended to be used in boolean contexts. Such classes also provide a user-defined conversion function explicit operator bool ( ) (see std::basic_ios for the standard library example), and the expected behavior of operator ! is to return the value opposite of operator bool .

Rarely overloaded operators

The following operators are rarely overloaded:

  • The address-of operator, operator & . If the unary & is applied to an lvalue of incomplete type and the complete type declares an overloaded operator & , the behavior is undefined (until C++11) it is unspecified whether the operator has the built-in meaning or the operator function is called (since C++11) . Because this operator may be overloaded, generic libraries use std::addressof to obtain addresses of objects of user-defined types. The best known example of a canonical overloaded operator& is the Microsoft class CComPtr . An example of its use in EDSL can be found in boost.spirit .
  • The boolean logic operators, operator && and operator || . Unlike the built-in versions, the overloads cannot implement short-circuit evaluation. Also unlike the built-in versions, they do not sequence their left operand before the right one. (until C++17) In the standard library, these operators are only overloaded for std::valarray .
  • The comma operator, operator, . Unlike the built-in version, the overloads do not sequence their left operand before the right one. (until C++17) Because this operator may be overloaded, generic libraries use expressions such as a, void ( ) ,b instead of a,b to sequence execution of expressions of user-defined types. The boost library uses operator, in boost.assign , boost.spirit , and other libraries. The database access library SOCI also overloads operator, .
  • The member access through pointer to member operator - > * . There are no specific downsides to overloading this operator, but it is rarely used in practice. It was suggested that it could be part of smart pointer interface , and in fact is used in that capacity by actors in boost.phoenix . It is more common in EDSLs such as cpp.react .

Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

  • Operator precedence
  • Alternative operator syntax
  • ↑ Operator Overloading on StackOverflow C++ FAQ

Learn C++

21.9 — Overloading the subscript operator

When working with arrays, we typically use the subscript operator ([]) to index specific elements of an array:

However, consider the following IntList class, which has a member variable that is an array:

Because the m_list member variable is private, we can not access it directly from variable list. This means we have no way to directly get or set values in the m_list array. So how do we get or put elements into our list?

Without operator overloading, the typical method would be to create access functions:

While this works, it’s not particularly user friendly. Consider the following example:

Are we setting element 2 to the value 3, or element 3 to the value 2? Without seeing the definition of setItem() , it’s simply not clear.

You could also just return the entire list and use operator[] to access the element:

While this also works, it’s syntactically odd:

Overloading operator[]

However, a better solution in this case is to overload the subscript operator ([]) to allow access to the elements of m_list. The subscript operator is one of the operators that must be overloaded as a member function. An overloaded operator[] function will always take one parameter: the subscript that the user places between the hard braces. In our IntList case, we expect the user to pass in an integer index, and we’ll return an integer value back as a result.

Now, whenever we use the subscript operator ([]) on an object of our class, the compiler will return the corresponding element from the m_list member variable! This allows us to both get and set values of m_list directly.

This is both easy syntactically and from a comprehension standpoint. When list[2] evaluates, the compiler first checks to see if there’s an overloaded operator[] function. If so, it passes the value inside the hard braces (in this case, 2) as an argument to the function.

Note that although you can provide a default value for the function parameter, actually using operator[] without a subscript inside is not considered a valid syntax, so there’s no point.

C++23 adds support for overloading operator[] with multiple subscripts.

Why operator[] returns a reference

Let’s take a closer look at how list[2] = 3 evaluates. Because the subscript operator has a higher precedence than the assignment operator, list[2] evaluates first. list[2] calls operator[], which we’ve defined to return a reference to list.m_list[2] . Because operator[] is returning a reference, it returns the actual list.m_list[2] array element. Our partially evaluated expression becomes list.m_list[2] = 3 , which is a straightforward integer assignment.

In lesson 12.2 -- Value categories (lvalues and rvalues) , you learned that any value on the left hand side of an assignment statement must be an l-value (which is a variable that has an actual memory address). Because the result of operator[] can be used on the left hand side of an assignment (e.g. list[2] = 3 ), the return value of operator[] must be an l-value. As it turns out, references are always l-values, because you can only take a reference of variables that have memory addresses. So by returning a reference, the compiler is satisfied that we are returning an l-value.

Consider what would happen if operator[] returned an integer by value instead of by reference. list[2] would call operator[], which would return the value of list.m_list[2]. For example, if m_list[2] had the value of 6, operator[] would return the value 6. list[2] = 3 would partially evaluate to 6 = 3 , which makes no sense! If you try to do this, the C++ compiler will complain:

Overloaded operator[] for const objects

In the above IntList example, operator[] is non-const, and we can use it as an l-value to change the state of non-const objects. However, what if our IntList object was const? In this case, we wouldn’t be able to call the non-const version of operator[] because that would allow us to potentially change the state of a const object.

The good news is that we can define a non-const and a const version of operator[] separately. The non-const version will be used with non-const objects, and the const version with const-objects.

Removing duplicate code between const and non-const overloads

In the above example, note that the implementations of int& IntList::operator[](int) and const int& IntList::operator[](int) const are identical. The only difference is the return type of the function.

In cases where the implementation is trivial (e.g. a single line), it’s fine (and preferred) to have both functions use an identical implementation. The small amount of redundancy this introduces isn’t worth removing.

But what if the implementation of these operators was complex, requiring many statements? For example, maybe it’s important that we validate that the index is actually valid, which requires adding many redundant lines of code to each function.

In such a case, the redundancy introduced by having many duplicate statements is more problematic, and it would be desirable to have a single implementation that we could use for both overloads. But how? Normally we’d simply implement one function in terms of the other (e.g. have one function call the other). But that’s a bit tricky in this case. The const version of the function can’t call the non-const version of the function, because that would require discarding the const of a const object. And while the non-const version of the function can call the const version of the function, the const version of the function returns a const reference, when we need to return a non-const reference. Fortunately, there is a way to work around this.

The preferred solution is as follows:

  • Refactor the logic into another function (usually a private const member function, but could be a non-member function).
  • Have the non-const function call the const function and use const_cast to remove the const of the returned reference.

The resulting solution looks something like this:

Normally using const_cast to remove const is something we want to avoid, but in this case it’s acceptable. If the non-const overload was called, then we know we’re working with a non-const object. It’s okay to remove the const on a const reference to a non-const object.

For advanced readers

In C++23, we can do even better by making use of several features we don’t yet cover in this tutorial series:

Detecting index validity

One other advantage of overloading the subscript operator is that we can make it safer than accessing arrays directly. Normally, when accessing arrays, the subscript operator does not check whether the index is valid. For example, the compiler will not complain about the following code:

However, if we know the size of our array, we can make our overloaded subscript operator check to ensure the index is within bounds:

In the above example, we have used the assert() function (included in the cassert header) to make sure our index is valid. If the expression inside the assert evaluates to false (which means the user passed in an invalid index), the program will terminate with an error message, which is much better than the alternative (corrupting memory). This is probably the most common method of doing error checking of this sort.

If you don’t want to use an assert (which will be compiled out of a non-debug build) you can instead use an if-statement and your favorite error handling method (e.g. throw an exception, call std::exit , etc…):

Pointers to objects and overloaded operator[] don’t mix

If you try to call operator[] on a pointer to an object, C++ will assume you’re trying to index an array of objects of that type.

Consider the following example:

Because we can’t assign an integer to an IntList, this won’t compile. However, if assigning an integer was valid, this would compile and run, with undefined results.

Make sure you’re not trying to call an overloaded operator[] on a pointer to an object.

The proper syntax would be to dereference the pointer first (making sure to use parenthesis since operator[] has higher precedence than operator*), then call operator[]:

This is ugly and error prone. Better yet, don’t set pointers to your objects if you don’t have to.

The function parameter does not need to be an integral type

As mentioned above, C++ passes what the user types between the hard braces as an argument to the overloaded function. In most cases, this will be an integral value. However, this is not required -- and in fact, you can define that your overloaded operator[] take a value of any type you desire. You could define your overloaded operator[] to take a double, a std::string, or whatever else you like.

As a ridiculous example, just so you can see that it works:

As you would expect, this prints:

Overloading operator[] to take a std::string parameter can be useful when writing certain kinds of classes, such as those that use words as indices.

Question #1

A map is a class that stores elements as a key-value pair. The key must be unique, and is used to access the associated pair. In this quiz, we’re going to write an application that lets us assign grades to students by name, using a simple map class. The student’s name will be the key, and the grade (as a char) will be the value.

a) First, write a struct named StudentGrade that contains the student’s name (as a std::string ) and grade (as a char ).

Show Solution

b) Add a class named GradeMap that contains a std::vector of StudentGrade named m_map .

c) Write an overloaded operator[] for this class. This function should take a std::string parameter, and return a reference to a char. In the body of the function, first see if the student’s name already exists (You can use std::find_if from <algorithm>). If the student exists, return a reference to the grade and you’re done. Otherwise, use the std::vector::emplace_back() function to add a StudentGrade for this new student. When you do this, std::vector will add a copy of your StudentGrade to itself (resizing if needed, invalidating all previously returned references). Finally, we need to return a reference to the grade for the student we just added to the std::vector . We can access the student we just added using the std::vector::back() function.

The following program should run:

See 20.6 -- Introduction to lambdas (anonymous functions) for more information on lambdas.

Since maps are common, the standard library offers std::map , which is not currently covered on learncpp. Using std::map , we can simplify our code to

Prefer using std::map over writing your own implementation.

Question #2

Extra credit #1: The GradeMap class and sample program we wrote is inefficient for many reasons. Describe one way that the GradeMap class could be improved.

std::vector is unsorted by nature. This means every time we call operator[] , we’re potentially traversing the entire std::vector to find our element. With a few elements, this isn’t a problem, but as we continue to add names, this will become increasingly slow. We could optimize this by keeping our m_map sorted and using a binary search, so we minimize the number of elements we have to look through to find the ones we’re interested in.

Question #3

Extra credit #2: Why does this program potentially not work as expected?

When Frank is added, the std::vector may need to grow to hold it. This requires dynamically allocating a new block of memory, copying the elements in the array to that new block, and deleting the old block. When this happens, any references to existing elements in the std::vector are invalidated (meaning they are left as dangling references to deleted memory).

In other words, after we emplace_back("Frank") , if the std::vector had to grow to make room for Frank, the gradeJoe reference would be invalidated. Accessing gradeJoe to print Joe’s grade would then lead to undefined results.

How a std::vector grows is a compiler-specific detail, so we can expect the above program to work fine when compiled with some compilers and not others.

guest

C++ Tutorial

  • C++ Overview
  • C++ Environment Setup
  • C++ Basic Syntax
  • C++ Comments
  • C++ Data Types
  • C++ Variable Types
  • C++ Variable Scope
  • C++ Constants/Literals
  • C++ Modifier Types
  • C++ Storage Classes
  • C++ Operators
  • C++ Loop Types
  • C++ Decision Making
  • C++ Functions
  • C++ Numbers
  • C++ Strings
  • C++ Pointers
  • C++ References
  • C++ Date & Time
  • C++ Basic Input/Output
  • C++ Data Structures
  • C++ Object Oriented
  • C++ Classes & Objects
  • C++ Inheritance
  • C++ Overloading
  • C++ Polymorphism
  • C++ Abstraction
  • C++ Encapsulation
  • C++ Interfaces
  • C++ Advanced
  • C++ Files and Streams
  • C++ Exception Handling
  • C++ Dynamic Memory
  • C++ Namespaces
  • C++ Templates
  • C++ Preprocessor
  • C++ Signal Handling
  • C++ Multithreading
  • C++ Web Programming
  • C++ Useful Resources
  • C++ Questions and Answers
  • C++ Quick Guide
  • C++ STL Tutorial
  • C++ Standard Library
  • C++ Discussion
  • Selected Reading
  • UPSC IAS Exams Notes
  • Developer's Best Practices
  • Questions and Answers
  • Effective Resume Writing
  • HR Interview Questions
  • Computer Glossary

Assignment Operators Overloading in C++

You can overload the assignment operator (=) just as you can other operators and it can be used to create an object just like the copy constructor.

Following example explains how an assignment operator can be overloaded.

When the above code is compiled and executed, it produces the following result −

operator overloading

Customizes the C++ operators for operands of user-defined types.

[ edit ] Syntax

Overloaded operators are functions with special function names:

[ edit ] Overloaded operators

When an operator appears in an expression , and at least one of its operands has a class type or an enumeration type , then overload resolution is used to determine the user-defined function to be called among all the functions whose signatures match the following:

Note: for overloading user-defined conversion functions , user-defined literals , allocation and deallocation see their respective articles.

Overloaded operators (but not the built-in operators) can be called using function notation:

[ edit ] Restrictions

  • The operators :: (scope resolution), . (member access), .* (member access through pointer to member), and ?: (ternary conditional) cannot be overloaded
  • New operators such as ** , <> , or &| cannot be created
  • The overloads of operators && , || , and , (comma) lose their special properties: short-circuit evaluation and sequencing .
  • The overload of operator -> must either return a raw pointer or return an object (by reference or by value), for which operator -> is in turn overloaded.
  • It is not possible to change the precedence, grouping, or number of operands of operators.

[ edit ] Canonical implementations

Other than the restrictions above, the language puts no other constraints on what the overloaded operators do, or on the return type (it does not participate in overload resolution), but in general, overloaded operators are expected to behave as similar as possible to the built-in operators: operator + is expected to add, rather than multiply its arguments, operator = is expected to assign, etc. The related operators are expected to behave similarly ( operator + and operator + = do the same addition-like operation). The return types are limited by the expressions in which the operator is expected to be used: for example, assignment operators return by reference to make it possible to write a = b = c = d , because the built-in operators allow that. Commonly overloaded operators have the following typical, canonical forms: [1]

[ edit ] Assignment operator

The assignment operator ( operator = ) has special properties: see copy assignment and move_assignment for details. To summarize, the canonical "universal assignment operator" implementation is

When there are resources that can be reused in assignment, for example, if the class owns a heap-allocated array, then copy-assignment between arrays of the same size can avoid allocation and deallocation:

[ edit ] Stream extraction and insertion

The overloads of operator>> and operator<< that take a std:: istream & or std:: ostream & as the left hand argument are known as insertion and extraction operators. Since they take the user-defined type as the right argument ( b in a@b ), they must be implemented as non-members.

These operators are sometimes implemented as friend functions .

[ edit ] Function call operator

When a user-defined class overloads the function call operator, operator ( ) , it becomes a FunctionObject type. Many standard algorithms, from std:: sort to std:: accumulate accept objects of such types to customize behavior. There are no particularly notable canonical forms of operator ( ) , but to illustrate the usage

[ edit ] Increment and decrement

When the postfix increment and decrement appear in an expression, the corresponding user-defined function ( operator ++ or operator -- ) is called with an integer argument 0 . Typically, it is implemented as T operator ++ ( int ) , where the argument is ignored. The postfix increment and decrement operator is usually implemented in terms of the prefix version:

Although canonical form of pre-increment/pre-decrement returns a reference, as with any operator overload, the return type is user-defined; for example the overloads of these operators for std::atomic return by value.

[ edit ] Binary arithmetic operators

Binary operators are typically implemented as non-members to maintain symmetry (for example, when adding a complex number and an integer, if operator+ is a member function of the complex type, then only complex + integer would compile, and not integer + complex ). Since for every binary arithmetic operator there exists a corresponding compound assignment operator, canonical forms of binary operators are implemented in terms of their compound assignments:

[ edit ] Relational operators

Standard algorithms such as std:: sort and containers such as std:: set expect operator < to be defined, by default, for the user-provided types. Typically, operator < is provided and the other relational operators are implemented in terms of operator < .

Likewise, the inequality operator is typically implemented in terms of operator == :

[ edit ] Array subscript operator

User-defined classes that provide array-like access that allows both reading and writing typically define two overloads for operator [ ] : const and non-const variants:

If the value type is known to be a built-in type, the const variant should return by value.

Where direct access to the elements of the container is not wanted or not possible or distinguishing between l-value c [ i ] = v ; and r-value v = c [ i ] ; usage, operator[] may return a proxy. see for example std::bitset::operator[] .

To provide multidimensional array access semantics, e.g. to implement a 3D array access a [ i ] [ j ] [ k ] = x ; , operator[] has to return a reference to a 2D plane, which has to have its own operator[] which returns a reference to a 1D row, which has to have operator[] which returns a reference to the element. To avoid this complexity, some libraries opt for overloading operator ( ) instead, so that 3D access expressions have the Fortran-like syntax a ( i,j,k ) = x ;

[ edit ] Example

[ edit ] see also.

  • Operator precedence
  • Alternative operator syntax

[ edit ] References

  • ↑ Operator Overloading on StackOverflow C++ FAQ

COMMENTS

  1. c++

    It makes no sense to return a reference. The operation must create a new object. - juanchopanza Jan 31, 2014 at 16:43 6 You don't want to be able to code a + b = c; - manuell Jan 31, 2014 at 16:45 Add a comment 6 Answers Sorted by: 25 Returning a reference from assignment allows chaining:

  2. operator overloading

    The canonical copy-assignment operator is expected to be safe on self-assignment, and to return the lhs by reference: or at least leave the object in a valid state on self-assignment, and return the lhs by reference to non-const, and be noexcept:

  3. 21.12

    5/3 This should all be pretty straightforward by now. Our overloaded operator= returns *this, so that we can chain multiple assignments together: int main() { Fraction f1 { 5, 3 }; Fraction f2 { 7, 2 }; Fraction f3 { 9, 5 }; f1 = f2 = f3; // chained assignment return 0; } Issues due to self-assignment

  4. C++ Operator Overloading Guidelines

    The value that should be returned is a reference to the left-hand side of the assignment. Notice that the returned reference is not declared const . This can be a bit confusing, because it allows you to write crazy stuff like this: MyClass a, b, c; ... (a = b) = c; // What??

  5. Assignment operators

    Assignment operators modify the value of the object. Syntax Over load able Prototype examples (for replaces the contents of the object is not modified). For class types, this is performed in a special member function, described in For non-class types, copy and move assignment are indistinguishable and are referred to as

  6. C++ Assignment Operator Overloading

    Courses Practice Prerequisite: Operator Overloading The assignment operator,"=", is the operator used for Assignment. It copies the right value into the left value. Assignment Operators are predefined to operate only on built-in Data types. Assignment operator overloading is binary operator overloading.

  7. Copy assignment operator

    Triviality of eligible copy assignment operators determines whether the class is a trivially copyable type. [] NoteIf both copy and move assignment operators are provided, overload resolution selects the move assignment if the argument is an rvalue (either a prvalue such as a nameless temporary or an xvalue such as the result of std::move), and selects the copy assignment if the argument is an ...

  8. How to Implement Assignment Operator Overloading in C++

    The solution to this is to define an overloaded assignment operator i.e., copy-assignment operator. The next code snippet implements the version of the Person class that can copy assign the two objects of the same class correctly. Notice, though, the if statement in the copy-assignment function guarantees that the operator works correctly even ...

  9. Why must the copy assignment operator return a reference/const

    In C++, the concept of returning reference from the copy assignment operator is unclear to me. Why can't the copy assignment operator return a copy of the new object? In addition, if I have class A, and the following: A a1 (param); A a2 = a1; A a3; a3 = a2; //<--- this is the problematic line The operator= is defined as follows:

  10. C++ overloading: operators to overload as methods of a class

    The following is the complete code to illustrate the overloaded assignment operator for the Array class. The overloaded operator performs the complete assignment and returns the LHS object with its updated values. ... Using a reference return type allows the change in the data member of an object. Therefore, such methods can't be declared const.

  11. PDF Introduction to C++ Operator Overloading

    Operator Overloading All arithmetic, bitwise, relational, equality, logical, and compound assignment operators can be overloaded. In addition, the address-of, dereference, increment, decrement, and comma operators can be overloaded. Operators that cannot be overloaded include::: scope resolution operator. direct member access operator

  12. Operator Overloading : Return by Referen

    Having an operator return by-value or by-reference really should depend on the operator - it should reflect the way that the operator is expected to work, because if you do it differently, then your operator may behave in an unusual fashion relative to its behaviour for other types such as int or std::string.

  13. operator overloading

    The return types are limited by the expressions in which the operator is expected to be used: for example, assignment operators return by reference to make it possible to write a=b=c=d, because the built-in operators allow that. Commonly overloaded operators have the following typical, canonical forms: [1] Assignment operator

  14. Why does overloaded assignment operator return reference to class?

    3 Answers Sorted by: 10 It is not "wrong", but surprising. An assignment evaluates to the target object. That's what the builtin meaning is. If you define it different for your own class, people could become confused. Example: int c; while ( (c = getchar ()) != EOF) { // ... }

  15. operator overloading

    The return types are limited by the expressions in which the operator is expected to be used: for example, assignment operators return by reference to make it possible to write a = b = c = d, because the built-in operators allow that. Commonly overloaded operators have the following typical, canonical forms: [1] Assignment operator

  16. Why assignment operator overloading must return reference

    The reason you should return a mutable reference from your assignment operator is that not doing so causes your returned value to be copied each time your assignment operator is called.

  17. 21.9

    An overloaded operator [] function will always take one parameter: the subscript that the user places between the hard braces. In our IntList case, we expect the user to pass in an integer index, and we'll return an integer value back as a result.

  18. Why a reference is returned in an assignment operator overload?

    Why a reference is returned in an assignment operator overload? Ask Question Asked 11 years, 6 months ago Modified 11 years, 6 months ago Viewed 763 times 0 I read that, reference is returned from a overloaded assignment operator to enable operator chaining. But without that return also, operator chaining seems to work.

  19. Assignment Operators Overloading in C++

    Assignment Operators Overloading in C++ You can overload the assignment operator (=) just as you can other operators and it can be used to create an object just like the copy constructor. Following example explains how an assignment operator can be overloaded. Live Demo

  20. operator overloading

    The overloads of operators &&, ||, and , (comma) lose their special properties: short-circuit evaluation and sequencing . The overload of operator -> must either return a raw pointer or return an object (by reference or by value), for which operator -> is in turn overloaded. It is not possible to change the precedence, grouping, or number of ...

  21. overloading operator = return by value or reference?

    5 Answers Sorted by: 5 Those are very different Do you want chained assignments? Return *this. a = b = c; // Chained assignment Default assignment in C++ supports chaining, so it's a good idea to keep this rule. To support this type of assignment each operation has to return a reference to *this or return a copy: