According to cppreference.com, std::rel_ops::operator!=,>,<=,>= will be deprecated in C++20.
What's the rationale behind?
In C++20, you get three-way comparison (operator <=>), which automatically "generates" default comparisons if provided:
struct A {
// You only need to implement a single operator.
std::strong_ordering operator<=>(const A&) const;
};
// Compiler generates 4 relational operators (you need to default the
// three-way comparison operator to get == and !=).
A to1, to2;
if (to1 > to2) { /* ... */ } // ok
if (to1 <= to2) { /* ... */ } // ok, single call to <=>
There are multiple advantages of the three-way comparison over std::rel_ops, which is probably why std::rel_ops operators are deprecated. On top of my head:
It is more versatile, since, depending on the return type of operator<=> (std::strong_ordering, std::weak_ordering, ...), only relevant operators are generated. See the <compare> header for more information.
You do not bring a bunch of templated operator overloads by doing using namespace std::rel_ops.
You can ask the compiler to generate the three-way operator for you by defaulting it (auto operator<=>(A const&) = default) — This will basically generate a lexicographic comparison of base classes and non-static data members, plus it will deduce the right type of ordering if the return type is auto.
What's the rationale behind?
rel_ops was deprecated by Library Support for the Spaceship (Comparison) Operator. The paper doesn't list any motivation, but it does appear in the spaceship paper:
This subsumes namespace std::rel_ops, so we propose also removing (or deprecating) std::rel_ops.
There are four reasons mentioned in the paper (including correctness and performance). But one big one not mentioned in either paper is that std::rel_ops just... doesn't work. Rule of thumb is that operators are found using ADL. rel_ops doesn't give you ADL-findable operators, it just declares unconstrained function templates like:
namespace std {
namespace rel_ops {
template< class T >
bool operator!=( const T& lhs, const T& rhs )
{
return !(lhs == rhs);
}
}
}
So using algorithms like:
struct X { ... };
bool operator<(X const&, X const&) { ... };
std::sort(values.begin(), values.end(), std::greater<>{});
Just doesn't work, unless you make sure to:
#include <utility>
using namespace std::rel_ops;
Fairly consistently everywhere as your first include to ensure that these operators are visible at the point of definition of every function template you could possibly call.
So operator<=> is just strictly superior:
It actually works.
You only have to write one function (<=>) instead of two (== and <)
Typically, you actually have to write zero functions (= default)
Did I mention it actually works?
C++20 provides Three way comparison thus the unique ones will become deprecated.
Related
Note: I presume that this is technically duplicate of this question but:
changes to == in C++20 are quite radical, and I am not sure if
reviving 9 year question is the proper thing to do.
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.
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.
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
I've been combing through the internet to find an answer, but I couldn't find any. The only reasons given seems to be relevant for comparing with objects of different type (e.g. MyClass == int). But the most common use case is comparing a class instance to another instance of the same class, not to any unrelated type.
In other words, I do understand the problems with:
struct A {
bool operator==(int b);
};
But I cannot find any good reason to not use member function in the most obvious use-case:
struct A {
bool operator==(const A&);
};
The most canonical duplicate What are the basic rules and idioms for operator overloading? says "overload binary operators as non-member" as rule of a thumb.
Operator overloading : member function vs. non-member function? gives example mentioned above - if you were to use this operator with instance of another class/primitive type...
CppCoreGuidelines has a vague explanation "If you use member functions, you need two", which I assume applies to comparing with object of different type.
Why should operator< be non-member function? mentions that "non-member functions play better with implicit conversion", but it seems again the case of left-hand operand not being the instance of the class.
On the other hand, member overload seems to have a couple positive sides:
No need to befriend the function or to provide getters for members
It is always available to class users (although this might be the downside also)
No problems with lookup (which seems to be common in our GoogleTests for some reason)
Is overloading operator== as non-member function just a convention to keep it the same with possible overloads in other classes? Or are there any other reasons to make it non-member?
Well, in your question, you did forget to const qualify the member function, and it would be harder to write bool operator==(A&, const A&); by accident.
If you had an implicit constructor, a class with implicit conversion to A or base class with an operator== with higher priority, the member function wouldn't work if it was on the left, but would if it was on the right. Although most of the time implicit conversions are a bad idea, inheritance could reasonably lead to a problem.
struct A {
A(int); // Implicit constructor
A();
bool operator==(const A&) const;
};
struct B : A {
bool operator==(const B&) const;
};
void test() {
A a;
B b;
// 1 == a; // Doesn't work
a == 1;
// b == a; // Doesn't work; Picks `B::operator==(const B&) const;`
a == b; // Picks `A::operator==(const A&) const`, converting `b` to an `A&`.
// Equality is no longer symmetric as expected
}
In the future, with the C++20 operator<=>, you will most likely always implement this as a member function (namely as auto operator<=>(const T&) const = default;), so we know that this guideline may change.
The arguments for using non-member operator overload for symmetric operations are based on style and consistency. They are not very strong arguments 1. Non-member overloads are typically preferred because a weak argument is still a little bit better than no argument at all.
Your arguments for member operator overload don't seem to be any stronger. Consider following:
No need to befriend the function
On the other hand, if you use a non-member overload, then you don't have need to declare a member function. Is befriending the non-member somehow worse?
or to provide getters for members
There is no need for that if you befriend the overload.
It is always available to class users (although this might be the downside also)
It is unclear how this differs from the non-member overloads. Are they also not always available to the class users?
No problems with lookup (which seems to be common in our GoogleTests for some reason)
Are there lookup problems with non-member overloads? Could you demonstrate the problem with an example, and show how the problem is solved by using a member overload instead?
If it does solve the problem, then you can of course use that. Just because some guidelines recommend that you prefer one alternative as a rule of thumb, doesn't mean that is the only alternative to be used in all use cases.
1 Although, see answer https://stackoverflow.com/a/57927564/2079303 which is arguably stronger than just stylistic.
I've been combing through the internet to find an answer, but I couldn't find any. The only reasons given seems to be relevant for comparing with objects of different type (e.g. MyClass == int). But the most common use case is comparing a class instance to another instance of the same class, not to any unrelated type.
In other words, I do understand the problems with:
struct A {
bool operator==(int b);
};
But I cannot find any good reason to not use member function in the most obvious use-case:
struct A {
bool operator==(const A&);
};
The most canonical duplicate What are the basic rules and idioms for operator overloading? says "overload binary operators as non-member" as rule of a thumb.
Operator overloading : member function vs. non-member function? gives example mentioned above - if you were to use this operator with instance of another class/primitive type...
CppCoreGuidelines has a vague explanation "If you use member functions, you need two", which I assume applies to comparing with object of different type.
Why should operator< be non-member function? mentions that "non-member functions play better with implicit conversion", but it seems again the case of left-hand operand not being the instance of the class.
On the other hand, member overload seems to have a couple positive sides:
No need to befriend the function or to provide getters for members
It is always available to class users (although this might be the downside also)
No problems with lookup (which seems to be common in our GoogleTests for some reason)
Is overloading operator== as non-member function just a convention to keep it the same with possible overloads in other classes? Or are there any other reasons to make it non-member?
Well, in your question, you did forget to const qualify the member function, and it would be harder to write bool operator==(A&, const A&); by accident.
If you had an implicit constructor, a class with implicit conversion to A or base class with an operator== with higher priority, the member function wouldn't work if it was on the left, but would if it was on the right. Although most of the time implicit conversions are a bad idea, inheritance could reasonably lead to a problem.
struct A {
A(int); // Implicit constructor
A();
bool operator==(const A&) const;
};
struct B : A {
bool operator==(const B&) const;
};
void test() {
A a;
B b;
// 1 == a; // Doesn't work
a == 1;
// b == a; // Doesn't work; Picks `B::operator==(const B&) const;`
a == b; // Picks `A::operator==(const A&) const`, converting `b` to an `A&`.
// Equality is no longer symmetric as expected
}
In the future, with the C++20 operator<=>, you will most likely always implement this as a member function (namely as auto operator<=>(const T&) const = default;), so we know that this guideline may change.
The arguments for using non-member operator overload for symmetric operations are based on style and consistency. They are not very strong arguments 1. Non-member overloads are typically preferred because a weak argument is still a little bit better than no argument at all.
Your arguments for member operator overload don't seem to be any stronger. Consider following:
No need to befriend the function
On the other hand, if you use a non-member overload, then you don't have need to declare a member function. Is befriending the non-member somehow worse?
or to provide getters for members
There is no need for that if you befriend the overload.
It is always available to class users (although this might be the downside also)
It is unclear how this differs from the non-member overloads. Are they also not always available to the class users?
No problems with lookup (which seems to be common in our GoogleTests for some reason)
Are there lookup problems with non-member overloads? Could you demonstrate the problem with an example, and show how the problem is solved by using a member overload instead?
If it does solve the problem, then you can of course use that. Just because some guidelines recommend that you prefer one alternative as a rule of thumb, doesn't mean that is the only alternative to be used in all use cases.
1 Although, see answer https://stackoverflow.com/a/57927564/2079303 which is arguably stronger than just stylistic.
Is it possible to redefine operator < for strings without modifying std namespace, to make this operator use in standard algorithms?
For example, I can write:
namespace std
{
bool operator <(const std::string & rhs, const std::string & lhs)
{
std::cout << "lol";
return false;
}
}
int main()
{
std::vector<std::string> lol = { "a", "b", "ba", "aa" };
std::sort(lol.begin(), lol.end());
}
and "lol" will be printed several times. But if I move operator < outside from std namespace, default operator < will be used and nothing will be printed. Is it possible to make std::sort using custom operator < without including it to std namespace?
Yes I know, I can pass another comparator to std::sort but it's interesting for me if I could do what I asked and how?
Also am I correct, that it's correct to add such template specialization to std namespace?
Update: This is not practical question, I just want to know how can I do that if it's possible.
No, it is not. Adding a function to the standard namespace is undefined behavior. [namespace.std]/1 states:
The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.
If you want to change how std::sort sorts then you can provide a lambda and define what you want
std::sort(std::begin(foo), std::end(foo), [](const auto& lhs, const auto& rhs) { /* your code here */ });
Is it possible to redefine operator < for strings without modifiying std namespace
You can define the overload in another namespace, sure. But as you have found out, it will not be found by overload resolution unless explicitly qualified.
Is it possible to make std::sort using custom operator < without including it to std namespace?
Yes, and you already seem to know how:
Yes I know, I can pass another comparator to std::sort
This is exactly what the comparator argument is for.
Also am I correct, that it's corect to add such template specialization to std namespace?
That is not a template specialization; It is a function definition and you may not add function definitions to std namespace - or else the behaviour is undefined. You would be allowed to add template specializations, but only if at least one type argument is a user defined type.
Given a programmer defined POD struct that will be stored in an unordered_map, is there any particular advantage in defining:
namespace std {
template<>
struct equal_to<MyType> {
bool operator()(const MyType& lhs, const MyType& rhs) const {
...
}
};
}
over simply defining:
operator==(const MyType& lhs, const MyType& rhs)
(I'm already aware of the potential advantage of using an "inlineable" function object rather than a function pointer for the hashing function).
I would say operator== has more uses than a specialization of equal_to<> because people normally write a == b, not equal_to<T>()(a, b). And the default equal_to<> is implemented in terms of operator==, not the other way around.
If you need to specialize std::equal_to because it must behave differently from operator==, then a better idea may be to implement a custom my_equal_to predicate class, not related to std::equal_to in order to follow the principle of least surprise.
Also, there is interface deficiency in std::equal_to<T> because it accepts arguments of the same type. C++14 std::equal_to<void> fixes the deficiency by accepting arguments of different types and forwarding them to operator==.
operator==, on the other hand, can have multiple overloads for different types (e.g. operator==(std::string const&, char const*)).
Which means that in C++14 std::equal_to<void> and overloaded operator== work nicely together, see N3657 Adding heterogeneous comparison lookup to associative containers for more details.
I'm already aware of the potential advantage of using an "inlineable" function object rather than a function pointer for the hashing function
Function pointers do not apply here, default equal_to<> uses operator== directly, not through a pointer.
If you have a class hierarchy and want to use the equal_to of the parent class on two objects of the child class, you may use the equal_to-variant, but not the == variant (which will choose the comperator for the child class):