Search code examples
classc++11overloadingpass-by-referencefriend

Why does the return type of a friend function that is overloading the "<<" operator have to be a reference?


friend ostream &operator<<( ostream&, Complex& );

Why can't I just use

friend ostream operator<<( ostream&, Complex&);

Solution

  • It doesn't have to, but there are several reasons to explain why this is a common choice.

    Many STL classes and objects which deal with streams usually choose the return-by-reference since this allows chaining

    class MyClass {
      public:
    
      MyClass(int v) : value(v) {}
      int value;
    
      friend MyClass &operator<<( MyClass&, MyClass& );
    
    };
    
    MyClass &operator<< (MyClass& a, MyClass& b) {
        a.value += b.value;
        return a;
    }
    
    int main() {
        MyClass a(2);
        MyClass b(3);
        MyClass c(5);
    
        a << b << c; // Chaining
    
        std::cout << a.value; // 10
    }
    

    Live Example

    If you were to return by value, this could not be accomplished in a single line

    a << b; // Can't chain
    a << c;
    

    Another important reason regarding the abovementioned classes stems from the fact that dealing with streams means you can't copy a stream (https://stackoverflow.com/a/6010930/1938163) since it doesn't quite make sense.

    And this also means you can't return by value if you're keeping a stream object in the object's state (you'd get an implicitly deleted copy constructor)

    class MyClass {
      public:
    
      MyClass(std::string str) {
        ss << str;
      }
      std::stringstream ss; // This CANNOT be copied
    
      friend MyClass operator<<( MyClass&, MyClass& );
    
    };
    
    MyClass operator<< (MyClass& a, MyClass& b) {
        a.ss << b.ss.str();
        return a; // Can't do this! Copy ctor is implicitly deleted due to ss
    }
    

    Live Example

    In this latter, common case returning by reference is almost a must.

    Aside from these cases nobody forces you to return by reference though. It also depends on your use case and, more importantly, on how your object manages its resources (remember that there might be a destructor involved if you return by value if there's no RVO involved).