Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do I have to implement commutativity for comparison operators manually?

With n different classes, which should all be comparable with operator== and operator!=, it would be necessary to implement (n ^ 2 - n) * 2 operators manually. (At least I think that's the term)

That would be 12 for three classes, 24 for four. I know that I can implement a lot of them in terms of other operators like so:

operator==(A,B); //implemented elsewhere

operator==(B,A){ return A == B; }

operator!=(A,B){ return !(A == B); }

but it still seems very tedious, especially because A == B will always yield the same result as B == A and there seems to be no reason whatsoever to implement two version of them.

Is there a way around this? Do I really have to implement A == B and B == A manually?

like image 304
iFreilicht Avatar asked Jun 12 '14 18:06

iFreilicht


People also ask

How do you define a comparison operator in C++?

Comparison operators in C++ are the ones that are there to compare two values with each other such as “==”, “!= ”, “>”, “<”, “>=”, and “<=”. This article will share the methods of overloading all six of these comparison operators in C++ in Ubuntu 20.04.

Which comparison operator is used for comparing?

Comparison operators are used for comparing one expression to another. The result is always either TRUE, FALSE or NULL. The LIKE operator compares a character, string, or CLOB value to a pattern and returns TRUE if the value matches the pattern and FALSE if it does not.

What is three way comparison operator in C++?

The C++20 three-way comparison operator <=> (commonly nicknamed the spaceship operator due to its appearance) compares two items and describes the result. It's called the three-way comparison because there are five possible results: less, equal, equivalent, greater, and unordered.


3 Answers

Use Boost.Operators, then you only need to implement one, and boost will define the rest of the boilerplate for you.

struct A
{};

struct B : private boost::equality_comparable<B, A>
{
};

bool operator==(B const&, A const&) {return true;}

This allows instances of A and B to be compared for equality/inequality in any order.

Live demo

Note: private inheritance works here because of the Barton–Nackman trick.

like image 126
Praetorian Avatar answered Oct 16 '22 18:10

Praetorian


In the comments the problem is further explained by stating that all of the types are really different forms of smart pointers with some underlying type. Now this simplifies quite a lot the problem.

You can implement a generic template for the operation:

template <typename T, typename U>
bool operator==(T const & lhs, U const & rhs) {
   return std::addressof(*lhs) == std::addressof(*rhs);
}

Now this is a bad catch all (or rather catch too many) implementation. But you can narrow down the scope of the operator by providing a trait is_smart_ptr that detects whether Ptr1 and Ptr2 are one of your smart pointers, and then use SFINAE to filter out:

template <typename T, typename U,
          typename _ = typename std::enable_if<is_pointer_type<T>::value 
                                            && is_pointer_type<U>::value>::type >
bool operator==(T const & lhs, U const & rhs) {
   return std::addressof(*lhs) == std::addressof(*rhs);
}

The type trait itself can be just a list of specializations of a template:

template <typename T>
struct is_pointer_type : std::false_type {};

template <typename T>
struct is_pointer_type<T*> : std::true_type {};

template <typename T>
struct is_pointer_type<MySmartPointer<T>> : std::true_type {};

template <typename T>
struct is_pointer_type<AnotherPointer<T>> : std::true_type {};

It probably makes sense not to list all of the types that match the concept of pointer, but rather test for the concept, like:

template <typename T, typename U,
          typename _ = decltype(*declval<T>())>
bool operator==(T const & lhs, U const & rhs) {
   return std::addressof(*lhs) == std::addressof(*rhs);
}

Where the concept being tested is that it has operator* exists. You could extend the SFINAE check to verify that the stored pointer types are comparable (i.e. that std::addressof(*lhs) and std::addressof(*rhs) has a valid equality:

template <typename T, typename U,
          typename _ = decltype(*declval<T>())>
auto operator==(T const & lhs, U const & rhs) 
                -> decltype(std::addressof(*lhs) == std::addressof(*rhs))
{
   return std::addressof(*lhs) == std::addressof(*rhs);
}

And this is probably as far as you can really get: You can compare anything that looks like a pointer to two possibly unrelated objects, if raw pointers to those types are comparable. You might need to single out the case where both arguments are raw pointers to avoid this entering out into a recursive requirement...

like image 33
David Rodríguez - dribeas Avatar answered Oct 16 '22 17:10

David Rodríguez - dribeas


not necesarily:

template<class A, class B>
bool operator==(const A& a, const B& b)
{ return b==a; }

works for whatever A and B there is a B==A implementation (otherwise will recourse infinitely)

You can also use CRTP if you don't want the templetized == to work for everything:

template<class Derived>
class comparable {};

class A: public comparable<A>
{ ... };

class B: public comparable<B>
{ ... };

bool operator==(const A& a, const B& b) 
{ /* direct */ }

// this define all reverses
template<class T, class U>
bool operator==(const comparable<T>& sa, const comparable<U>& sb)
{ return static_cast<const U&>(sb) == static_cast<const T&>(sa); }

//this defines inequality
template<class T, class U>
bool operator!=(const comparable<T>& sa, const comparable<U>& sb)
{ return !(static_cast<const T&>(sa) == static_cast<const U&>(sb)); }

Using return type SFINAE yo ucan even do something like

template<class A, class B>
auto operator==(const A& a, const B& b) -> decltype(b==a) 
{ return b==a; }

template<class A, class B>
auto operator!=(const A& a, const B& b) -> decltype(!(a==b))
{ return !(a==b); }
like image 42
Emilio Garavaglia Avatar answered Oct 16 '22 16:10

Emilio Garavaglia