mistake

C++ Core Guidelines: Rules for Overloading and Overload Operators

There are ten rules for overloading and overload operators in the C++ core guidelines. Many of them are pretty obvious, but your software may become very unintuitive if you don’t follow them.

mistake

I’m pretty surprised to have only ten rules for overloading in the guidelines. I’m particularly surprised because I had a lot of discussions in the past about the overloading of operators in C++. Additionally, at least MISRA C++, which is heavily used in the automotive and embedded area, forbids operator overloading.

In contrast to the discussion in C++, I have not a discussion in Python about operator overloading in mind, but Python is heavily based on operator overloading. To get an idea. Look at the following special functions starting and ending with two underscores, lovingly called dunder in Python. You must implement them to get a type that behaves like an int.

python

 But let me continue with C++. Here are the ten rules.

A lot of the rules are quite obvious; therefore, I often can make it short.

Overloading and overloaded operators

C.160: Define operators primarily to mimic conventional usage

You should follow the principle of least astonishment. Or to say it in rule C: 61: A copy operation should copy. That means after the assignment x = y, x == y should hold.

This was obvious. Right? The following rule sounds easy, but the discussion of it is pretty enlighting.

Rainer D 6 P2 500x500

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    C.161: Use nonmember functions for symmetric operators

    Implementing a symmetric operator such as + is impossible inside the class.

    Assume that you want to implement a type MyInt. MyInt should support addition with MyInt‘s and built-in int‘s. Let’s give it a try.

    // MyInt.cpp
    
    struct MyInt{
        MyInt(int v):val(v){};              // 1
        MyInt operator+(const MyInt& oth) const { 
            return MyInt(val + oth.val);
        }
        int val;
    };
    
    int main(){
    
      MyInt myFive = MyInt(2) + MyInt(3);
      MyInt myFive2 = MyInt(3) + MyInt(2);
      
      MyInt myTen = myFive + 5;            // 2
      MyInt myTen2 = 5 + myFive;           // 3 ERROR
      
    }
      
    

     

    Because of the implicit conversion constructor (1), expression (2) is valid. In contrast, the last expression (3) is invalid because the 5 in the expression 5 + myFive will not implicitly convert to a MyInt; therefore, I got a compiler error.

    Trying to compile the program will fail because int does not have an overloaded + operator for MyInt

    MyIntError

    The minor program has a lot of issues:

    1. The + operator is not symmetric.
    2. The val variable is public.
    3. The conversion constructor is implicit.

    It’s pretty easy to overcome the first issues with a non-member operator + that is in the class declared as a friend.

     

    // MyInt2.cpp
    
    class MyInt2{
    public:
        MyInt2(int v):val(v){};
        friend MyInt2 operator+(const MyInt2& fir, const MyInt2& sec){ 
            return MyInt2(fir.val + sec.val);
        }
    private:
        int val;
    };
    
    int main(){
    
      MyInt2 myFive = MyInt2(2) + MyInt2(3);
      MyInt2 myFive2 = MyInt2(3) + MyInt2(2);
      
      MyInt2 myTen = myFive + 5;
      MyInt2 myTen2 = 5 + myFive;           
      
    }
      
    

     

    Now, implicit conversion from int to MyInt kicks in, and the variable val is private. According to rule C.164: Avoid conversion operators, you should not use an implicit conversion constructor. By making the conversion constructor in the following example explicit, the example will break.

    // MyInt3.cpp
    
    class MyInt3{
    public:
        explicit MyInt3(int v):val(v){};           // 1
        friend MyInt3 operator+(const MyInt3& fir, const MyInt3& sec){ 
            return MyInt3(fir.val + sec.val);
        }
    private:
        int val;
    };
    
    int main(){
    
      MyInt3 myFive = MyInt3(2) + MyInt3(3);
      MyInt3 myFive2 = MyInt3(3) + MyInt3(2);
      
      MyInt3 myTen = myFive + 5;                   // 2
      MyInt3 myTen2 = 5 + myFive;                  // 3
      
    }
    

     

    Thanks to the explicit conversion operator, implicit conversion from int to MyInt will not happen; therefore, lines (2) and (3) fail.

    MyInt3Error

    At least I followed the rule, and my operator is symmetric.

    One obvious way to solve the original challenge is to implement two additional + operators for MyInt4. One should take an int as left, and one should take an int as correct argument.

    // MyInt4.cpp
    
    class MyInt4{
    public:
        explicit MyInt4(int v):val(v){};           // 1
        friend MyInt4 operator+(const MyInt4& fir, const MyInt4& sec){ 
            return MyInt4(fir.val + sec.val);
        }
        friend MyInt4 operator+(const MyInt4& fir, int sec){
            return MyInt4(fir.val + sec);
        }
         friend MyInt4 operator+(int fir, const MyInt4& sec){
            return MyInt4(fir + sec.val);
        }
    private:
        int val;
    };
    
    int main(){
    
      MyInt4 myFive = MyInt4(2) + MyInt4(3);
      MyInt4 myFive2 = MyInt4(3) + MyInt4(2);
      
      MyInt4 myTen = myFive + 5;                   // 2
      MyInt4 myTen2 = 5 + myFive;                  // 3
      
    }
    

    C.162: Overload operations that are roughly equivalent, and C.163: Overload only for operations that are roughly equivalent

    This time, I can make it short. Equivalent operations should have the same name. Or the other way around. Non-equivalent operations should not have the same name.

    Here is an example from the guidelines:

    void print(int a);
    void print(int a, int base);
    void print(const string&);
    

     

    Invoking print(arg) with an argument feels like generic programming. You do not have to care which version of print will be used.

    This will not hold for the three following functions because they have different names:

    void print_int(int a);
    void print_based(int a, int base);
    void print_string(const string&);
    

     

    If non-equivalent operations have the same name, the names may be too general or wrong. This is confusing and error-prone.

    What’s next?

    The following rule is quite essential: C.164: Avoid conversion operators. I already mentioned it in rule C.161. You should not use an implicit conversion constructor and – this is new – an implicit conversion operator. Why? I will write about it in the next post.

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *