Should the operators == and <=> in C++ 20 be implemented as a member or a free function?
Asked Answered
R

2

7

Note: I presume that this is technically duplicate of this question but:

  1. changes to == in C++20 are quite radical, and I am not sure if reviving 9 year question is the proper thing to do.
  2. I ask specifically about the operators == and <=> that are being rewritten by compiler, not for example operator <.

p.s. I have my own opinion at the moment(based on some talk by foonathan), but it is just a current preference and I prefer not bias the potential answers with it.

Ring answered 21/5, 2020 at 8:17 Comment(2)
Can this be anything other than Opinion Based ?Shorten
AFAIK this choice has impact on function overload resolution.Neology
F
8

I would argue that in C++20, comparisons should be member functions unless you have a strongly compelling reason otherwise.

Lemme first start with the C++17 calculus: we would often write our comparisons as non-members. The reason for this is that it was the only way to allow two-sided comparisons. If I had a type X that I wanted to be comparable to int, I can't make 1 == X{} work with a member function - it has to be a free function:

struct X { };
bool operator==(X, int);
bool operator==(int lhs, X rhs) { return rhs == lhs; }

There wasn't much choice in the matter. Now, writing these as purely free functions is sub-optimal because we're polluting the namespace and increasing the amounts of candidates in lookup - so it's better to make them hidden friends:

struct X {
    friend bool operator==(X, int);
    friend bool operator==(int lhs, X rhs) { return rhs == lhs; }
};

In C++20, we don't have this issue because the comparisons are themselves symmetric. You can just write:

struct X {
    bool operator==(int) const;
};

And that declaration alone already allows both X{} == 1 and 1 == X{}, while also already not contributing extra candidates for name lookups (it will already only be a candidate if one side or the other is an X).

Moreover, in C++20, you can default comparisons if they're declared within the declaration of the class. These could be either member functions or hidden friends, but not external free functions.


One interesting case for a reason to provide non-member comparison is what I ran into with std::string. The comparisons for that type are currently non-member function templates:

 template<class charT, class traits, class Allocator>
    constexpr bool
      operator==(const basic_string<charT, traits, Allocator>& lhs,
                 const basic_string<charT, traits, Allocator>& rhs) noexcept;

This has importantly different semantics from making this a member (non-template) function or a hidden friend (non-template) function in that it doesn't allow implicit conversions, by way of being a template. As I pointed out, turning this comparison operator into a non-template would have the effect of suddenly allowing implicit conversions on both sides which can break code that wasn't previously aware of this possibility.

But in any case, if you have a class template and want to avoid conversions on your comparisons, that might be a good reason to stick with a non-member function template for your comparison operator. But that's about it.

Fonz answered 21/5, 2020 at 13:31 Comment(2)
BTW if you keep a wishlist for C++23 improvements here is my contribution: if a class is a simple wrapper(aka MyInt) that has only one member of type T then maybe would be nice if language would allow me to default comparison with that T, eg I have a Age (strong typed struct) with unsigned short as only member. Then bool operator == (uint16_t val) const == default; would be niceRing
@Ring I suppose this would require new keyword to work properly and not break too much of backward-compatibility. Anyways, since metaclasses can be added in next standard(s) it is possible that your wish will be fullfilled ;)Aryan
A
2

I'd argue from software engineering standpoint that always one should prefer to use free functions instead of member methods when possible. And I believe it is true for all functions. Why? It improves encapsulation and frees the function from "knowing" how the class is implemented. Of course, often comparison functions need to access private members and it is fine then to use friend or member function (still I'd prefer friend). Scott Meyers writes a bit about it in Effective C++, item 23

Here is article by Scott that reiterates this thought

Aryan answered 21/5, 2020 at 8:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.