167

That's basically the question, is there a "right" way to implement operator<< ? Reading this I can see that something like:

friend bool operator<<(obj const& lhs, obj const& rhs);

is preferred to something like

ostream& operator<<(obj const& rhs);

But I can't quite see why should I use one or the other.

My personal case is:

friend ostream & operator<<(ostream &os, const Paragraph& p) {
    return os << p.to_str();
}

But I could probably do:

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

What rationale should I base this decision on?

Note:

 Paragraph::to_str = (return paragraph) 

where paragraph's a string.

Robert
  • 1,286
  • 1
  • 17
  • 37
Federico Builes
  • 4,939
  • 4
  • 34
  • 48

8 Answers8

146

The problem here is in your interpretation of the article you link.

Equality

This article is about somebody that is having problems correctly defining the bool relationship operators.

The operator:

  • Equality == and !=
  • Relationship < > <= >=

These operators should return a bool as they are comparing two objects of the same type. It is usually easiest to define these operators as part of the class. This is because a class is automatically a friend of itself so objects of type Paragraph can examine each other (even each others private members).

There is an argument for making these free standing functions as this lets auto conversion convert both sides if they are not the same type, while member functions only allow the rhs to be auto converted. I find this a paper man argument as you don't really want auto conversion happening in the first place (usually). But if this is something you want (I don't recommend it) then making the comparators free standing can be advantageous.

Streaming

The stream operators:

  • operator << output
  • operator >> input

When you use these as stream operators (rather than binary shift) the first parameter is a stream. Since you do not have access to the stream object (its not yours to modify) these can not be member operators they have to be external to the class. Thus they must either be friends of the class or have access to a public method that will do the streaming for you.

It is also traditional for these objects to return a reference to a stream object so you can chain stream operations together.

#include <iostream>

class Paragraph
{
    public:
        explicit Paragraph(std::string const& init)
            :m_para(init)
        {}

        std::string const&  to_str() const
        {
            return m_para;
        }

        bool operator==(Paragraph const& rhs) const
        {
            return m_para == rhs.m_para;
        }
        bool operator!=(Paragraph const& rhs) const
        {
            // Define != operator in terms of the == operator
            return !(this->operator==(rhs));
        }
        bool operator<(Paragraph const& rhs) const
        {
            return  m_para < rhs.m_para;
        }
    private:
        friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
        std::string     m_para;
};

std::ostream & operator<<(std::ostream &os, const Paragraph& p)
{
    return os << p.to_str();
}


int main()
{
    Paragraph   p("Plop");
    Paragraph   q(p);

    std::cout << p << std::endl << (p == q) << std::endl;
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • 24
    Why is the `operator<<` `private:`? – Matt Clarkson Jan 25 '13 at 10:12
  • 54
    @MattClarkson: Its not. Its a friend function declaration thus not part of the class and thus not affected by the access specifiers. I generally put the friend function declarations next to the data that they accesses. – Martin York Jan 25 '13 at 10:16
  • Good link. Makes this answer even better! – Matt Clarkson Jan 25 '13 at 11:21
  • 13
    Why does it need to be a friendly function, if you are using public function to access data? Sorry, if the question is stupid. – Semyon Danilov Mar 29 '14 at 09:27
  • 9
    @SemyonDanilov: Why would you break encapsulation and add getters! `freiend` is a way to extended the public interface without breaking encapsulation. Read http://programmers.stackexchange.com/a/99595/12917 – Martin York Mar 29 '14 at 17:48
  • 8
    @LokiAstari But surely that's an argument for either removing to_str or making it private. As it stands, the streaming operator does not have to be a friend, as it uses only public functions. – deworde Mar 10 '17 at 13:49
  • @deworde: Sure. I have no issue with that. I (personally) would still make the `operator<<` a friend; not for access privileges but to document that it is part of the public API. But I would not be cry in a code review if it was not a friend. That comes down to personal taste. – Martin York Mar 10 '17 at 18:44
  • 1
    @LokiAstari And thus, the "unnecessary friend heresy" is allowed to propogate? I SAY THEE NAY SIR! [To the battlefield](https://blog.codinghorror.com/death-to-the-space-infidels/)! #YAllKnowItsTimeForAReligiousWar – deworde Mar 13 '17 at 09:27
  • 1
    @deworde: A the old 30/30/40 rule. 30% vigorously advocate one method; 30% vigorously advocate the exact opposite and 40% role their eyes and say let us know who wins. – Martin York Mar 13 '17 at 22:20
  • 1
    @deworde If you make it non-friend, and need to change it to access private members, you need to change the header, which is often ungood. For something that can be reasonably deemed a friend, whether or not it's currently taking advantage of that friendship, I think it's worthwhile to just stick `friend` on it upfront. – Lightness Races in Orbit Oct 17 '18 at 11:30
  • @MartinYork could you please take a look and update the [link](https://bytes.com/topic/c/answers/170304-compiler-says-operator-needs-exactly-one-argument-not-two-why) that you have mentioned at the end of the first line? It seems broken! Thank you! – Milan Oct 28 '20 at 00:07
59

You can not do it as a member function, because the implicit this parameter is the left hand side of the <<-operator. (Hence, you would need to add it as a member function to the ostream-class. Not good :)

Could you do it as a free function without friending it? That's what I prefer, because it makes it clear that this is an integration with ostream, and not a core functionality of your class.

Magnus Hoff
  • 21,529
  • 9
  • 63
  • 82
  • 1
    "not a core functionality of your class." That's what "friend" means. If it were core functionality, it would be in the class, not a friend. – xaxxon Jul 19 '16 at 07:14
  • 1
    @xaxxon I think my first sentence explains why it would be impossible in this case to add the function as a member function. A `friend` function has the same rights as a member function (_this_ is what `friend` means), so as a user of the class, I would have to wonder why it would need that. This is the distinction I am trying to make with the wording "core functionality". – Magnus Hoff Jul 21 '16 at 21:00
  • Could you give an example of `you do it as a free function`? – Evandro Coan Apr 20 '21 at 18:37
  • @user It means to place the function outside the class... e.g., `std::ostream& operator<<(std::ostream& os, const Foo& foo);` inside your header, and then implementing in your source. – FriskySaga Sep 23 '22 at 13:58
33

If possible, as non-member and non-friend functions.

As described by Herb Sutter and Scott Meyers, prefer non-friend non-member functions to member functions, to help increase encapsulation.

In some cases, like C++ streams, you won't have the choice and must use non-member functions.

But still, it does not mean you have to make these functions friends of your classes: These functions can still acess your class through your class accessors. If you succeed in writting those functions this way, then you won.

About operator << and >> prototypes

I believe the examples you gave in your question are wrong. For example;

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

I can't even start to think how this method could work in a stream.

Here are the two ways to implement the << and >> operators.

Let's say you want to use a stream-like object of type T.

And that you want to extract/insert from/into T the relevant data of your object of type Paragraph.

Generic operator << and >> function prototypes

The first being as functions:

// T << Paragraph
T & operator << (T & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// T >> Paragraph
T & operator >> (T & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return p_oInputStream ;
}

Generic operator << and >> method prototypes

The second being as methods:

// T << Paragraph
T & T::operator << (const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return *this ;
}

// T >> Paragraph
T & T::operator >> (const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return *this ;
}

Note that to use this notation, you must extend T's class declaration. For STL objects, this is not possible (you are not supposed to modify them...).

And what if T is a C++ stream?

Here are the prototypes of the same << and >> operators for C++ streams.

For generic basic_istream and basic_ostream

Note that is case of streams, as you can't modify the C++ stream, you must implement the functions. Which means something like:

// OUTPUT << Paragraph
template <typename charT, typename traits>
std::basic_ostream<charT,traits> & operator << (std::basic_ostream<charT,traits> & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> Paragraph
template <typename charT, typename traits>
std::basic_istream<charT,traits> & operator >> (std::basic_istream<charT,traits> & p_oInputStream, const CMyObject & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

For char istream and ostream

The following code will work only for char-based streams.

// OUTPUT << A
std::ostream & operator << (std::ostream & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> A
std::istream & operator >> (std::istream & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

Rhys Ulerich commented about the fact the char-based code is but a "specialization" of the generic code above it. Of course, Rhys is right: I don't recommend the use of the char-based example. It is only given here because it's simpler to read. As it is only viable if you only work with char-based streams, you should avoid it on platforms where wchar_t code is common (i.e. on Windows).

Hope this will help.

paercebal
  • 81,378
  • 38
  • 130
  • 159
  • Doesn't your generic basic\_istream and basic\_ostream templated code already cover the std::ostream- and std::istream-specific versions since the latter two are just instantiations of the former using chars? – Rhys Ulerich Dec 10 '09 at 15:18
  • 1
    @Rhys Ulerich: Of course. I use only the generic, templated version, if only because on Windows, you have to deal with both char and wchar_t code. The secondth version's only merit is to appear as more simple than the first. I'll clarify my post about that. – paercebal Dec 11 '09 at 13:15
11

It should be implemented as a free, non-friend functions, especially if, like most things these days, the output is mainly used for diagnostics and logging. Add const accessors for all the things that need to go into the output, and then have the outputter just call those and do formatting.

I've actually taken to collecting all of these ostream output free functions in an "ostreamhelpers" header and implementation file, it keeps that secondary functionality far away from the real purpose of the classes.

XPav
  • 1,150
  • 1
  • 8
  • 16
9

The signature:

bool operator<<(const obj&, const obj&);

Seems rather suspect, this does not fit the stream convention nor the bitwise convention so it looks like a case of operator overloading abuse, operator < should return bool but operator << should probably return something else.

If you meant so say:

ostream& operator<<(ostream&, const obj&); 

Then since you can't add functions to ostream by necessity the function must be a free function, whether it a friend or not depends on what it has to access (if it doesn't need to access private or protected members there's no need to make it friend).

Motti
  • 110,860
  • 49
  • 189
  • 262
  • It's worth mentioning access to modify `ostream` would be required when using the `ostream.operator<<(obj&)` ordering; hence the free function. Otherwise the user type need be a steam type to accommodate access. – wulfgarpro Oct 01 '16 at 23:46
5

Just for completion sake, I would like to add that you indeed can create an operator ostream& operator << (ostream& os) inside a class and it can work. From what I know it's not a good idea to use it, because it's very convoluted and unintuitive.

Let's assume we have this code:

#include <iostream>
#include <string>

using namespace std;

struct Widget
{
    string name;

    Widget(string _name) : name(_name) {}

    ostream& operator << (ostream& os)
    {
        return os << name;
    }
};

int main()
{
    Widget w1("w1");
    Widget w2("w2");

    // These two won't work
    {
        // Error: operand types are std::ostream << std::ostream
        // cout << w1.operator<<(cout) << '\n';

        // Error: operand types are std::ostream << Widget
        // cout << w1 << '\n';
    }

    // However these two work
    {
        w1 << cout << '\n';

        // Call to w1.operator<<(cout) returns a reference to ostream&
        w2 << w1.operator<<(cout) << '\n';
    }

    return 0;
}

So to sum it up - you can do it, but you most probably shouldn't :)

ashrasmun
  • 490
  • 6
  • 11
0

friend operator = equal rights as class

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}
Nehigienix
  • 53
  • 4
0

operator<< implemented as a friend function:

#include <iostream>
#include <string>
using namespace std;

class Samp
{
public:
    int ID;
    string strName; 
    friend std::ostream& operator<<(std::ostream &os, const Samp& obj);
};
 std::ostream& operator<<(std::ostream &os, const Samp& obj)
    {
        os << obj.ID<< “ ” << obj.strName;
        return os;
    }

int main()
{
   Samp obj, obj1;
    obj.ID = 100;
    obj.strName = "Hello";
    obj1=obj;
    cout << obj <<endl<< obj1;

} 

OUTPUT:
100 Hello
100 Hello

This can be a friend function only because the object is on the right hand side of operator<< and argument cout is on the left hand side. So this can't be a member function to the class, it can only be a friend function.

pkthapa
  • 1,029
  • 1
  • 17
  • 27
Rohit Vipin Mathews
  • 11,629
  • 15
  • 57
  • 112