What operators should be declared as friends?
Asked Answered
P

4

45

In some books and often around the internet I see recommendations like "operator== should be declared as friend".

How should I understand when an operator must be declared as friend and when it should be declared as member? What are the operators that will most often need to be declared as friends besides == and <<?

Peg answered 6/6, 2011 at 17:35 Comment(3)
Non-member functions that need access to private class members need to be friends.Charitycharivari
@AJG85: true, but I think the question is more about when to choose members, and when to choose non-members (making them friends if necessary).Cralg
We'll that's a bit subjective but I follow Scott Meyers advice and try to prefer non-member non-friend functions when possible. I would then prefer a member function over a friend in most cases. The best case for a friend function is probably in the case where you want a public interface that has implicit promotion of the left hand side.Charitycharivari
D
57

This really depends on whether a class is going to be on the left- or right-hand side of the call to operator== (or other operator). If a class is going to be on the right-hand side of the expression—and does not provide an implicit conversion to a type that can be compared with the left-hand side—you need to implement operator== as a separate function or as a friend of the class. If the operator needs to access private class data, it must be declared as a friend.

For example,

class Message {
    std::string content;
public:
    Message(const std::string& str);
    bool operator==(const std::string& rhs) const;
};

allows you to compare a message to a string

Message message("Test");
std::string msg("Test");
if (message == msg) {
    // do stuff...
}

but not the other way around

    if (msg == message) { // this won't compile

You need to declare a friend operator== inside the class

class Message {
    std::string content;
public:
    Message(const std::string& str);
    bool operator==(const std::string& rhs) const;
    friend bool operator==(const std::string& lhs, const Message& rhs);
};

or declare an implicit conversion operator to the appropriate type

class Message {
    std::string content;
public:
    Message(const std::string& str);
    bool operator==(const std::string& rhs) const;
    operator std::string() const;
};

or declare a separate function, which doesn't need to be a friend if it doesn't access private class data

bool operator==(const std::string& lhs, const Message& rhs);
Duvalier answered 6/6, 2011 at 17:40 Comment(2)
Good answer, but you don't need to declare the friend function outside the class definition. A single friend declaration inside the class (with an inline definition if you like) is enough.Cralg
@Mike Huh, you're right. For some reason, I didn't think that that was valid syntax...Duvalier
W
24

When you have your operators outside the class, both parameters can participate in implicit type conversions (whereas with operators being defined in the class body, only the right-hand operands can). Generally, that's a benefit for all the classic binary operators (i.e. ==,!=, +, -, <<, ... ).

Of course you should only declare operators friends of your class if you need to and not if they compute their result solely based on public members of the class.

Whelk answered 6/6, 2011 at 17:41 Comment(0)
P
10

No one has mentioned hidden friends idiom which I suspect is what the books mean.

Long version: https://www.justsoftwaresolutions.co.uk/cplusplus/hidden-friends.html

Short version:

Operators are found via ADL (argument dependent lookup) most of the time. This is how an operator== defined for std::string in std is found when you are not in namespace std.

One of the problems, common for operators is a gigantic overload set. You can often see this in error messages if you try to use operator<< for something that is not printable.

So - if you declare operator== in the namespace containing the class directly, it will work but it will also participate in all of the overload resolutions in that namespace which is going to slow down your compilation and give you more noise in the errors.

Introducing hidden friends:

struct X {
  friend bool operator==(const X& x, const X& y) {...}
};

This operator== will only be considered for overload resolution if one of the operands has type X. In all other cases it will not be seen, so your compilation will be faster and your error messages better.

Same goes for all two operand operators, like operator<< and also other functions intended for ADL, like swap.

I always define my operators like this and it's considered a good practise by quite a few people nowadays.

The only downside is - there is no very good way to define it out of line. You might want to consider dispatching to some private function. Or you can do this: https://godbolt.org/z/hMarb4 - but it means that at least in one cpp file the operator== will participate in normal lookup.

Pyrrolidine answered 3/1, 2021 at 13:38 Comment(0)
R
9

Generally, only operators which are implemented as free functions that genuinely need to access to private or protected data of the class that they operate on should be declared as friends, otherwise they should just be non-friend non-member functions.

Generally, the only operators that I implement as member functions are those that are fundamentally asymmetric and where the operands don't have equivalent roles. The ones that I tend to implement as members are those required to be members: simple assignment, (), [] and -> together with compound assignment operators, unary operators and perhaps some overloads of << and >> for classes that are themselves stream or stream-like classes. I never overload &&, || or ,.

All other operators I tend to implement as free functions, preferably using the public interface of the classes which they operate on, falling back to being friends only where necessary.

Keeping operators such as !=, ==, <, +, /, etc as non-member functions enables identical treatment of the left and right operands with respect to implicit conversion sequences which helps to reduce the number of surprising asymmetries.

Reticle answered 6/6, 2011 at 17:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.