13

I'm looking for an "is_comparable<>" typetrait but can't find any.

It's very easy to build one that checks if an operator== for a class was implemented, but this excludes global defined operators.

Is it impossible to implement a generic is_comparable<> typetrait?

vvv444
  • 2,764
  • 1
  • 14
  • 25
Gene
  • 410
  • 5
  • 16

1 Answers1

6

I take it you mean a trait that, for two types L and R and objects lhs and rhs of those types respectively, will yield true if the lhs == rhs will compile and false otherwise. You appreciate that in theory lhs == rhs might compile even though rhs == lhs, or lhs != rhs, does not.

In that case you might implement the trait like:

#include <type_traits>

template<class ...> using void_t = void;

template<typename L, typename R, class = void>
struct is_comparable : std::false_type {};

template<typename L, typename R>
using comparability = decltype(std::declval<L>() == std::declval<R>());

template<typename L, typename R>
struct is_comparable<L,R,void_t<comparability<L,R>>> : std::true_type{};

This applies a popular SFINAE pattern for defining traits that is explained in the answer to this question

Some illustrations:

struct noncomparable{};

struct comparable_right
{
    bool operator==(comparable_right const & other) const {
        return true;
    }
};

struct any_comparable_right
{
    template<typename T>
    bool operator==(T && other) const {
        return false;
    }
};

bool operator==(noncomparable const & lhs, int i) {
    return true;
}

#include <string>

static_assert(is_comparable<comparable_right,comparable_right>::value,"");
static_assert(!is_comparable<noncomparable,noncomparable>::value,"");
static_assert(!is_comparable<noncomparable,any_comparable_right>::value,"");
static_assert(is_comparable<any_comparable_right,noncomparable>::value,"");
static_assert(is_comparable<noncomparable,int>::value,"");
static_assert(!is_comparable<int,noncomparable>::value,"");
static_assert(is_comparable<char *,std::string>::value,"");
static_assert(!is_comparable<char const *,char>::value,"");
static_assert(is_comparable<double,char>::value,"");

If you want the trait to require that equality is symmetric and that inequality also exists and is symmetric you can see how to elaborate it yourself.

Community
  • 1
  • 1
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • Thx for your answer, but it doesn't work. Even your static_assert fails when trying to compile this code with gcc. The Problem seems to be the "std::declval() == std::declval()" which doesn't get evaluated. Interestingly clang-3.8 throws an compile exception as I would expect. – Gene Oct 18 '15 at 12:40
  • @Gene Odd, it compiled for me with [gcc 5.2 live](https://goo.gl/0Le5lw) and [clang 3.6 live](https://goo.gl/uLkdnm). Maybe you could post your failing compile? – Mike Kinghan Oct 18 '15 at 16:05
  • Interesting... I used gcc 4.9.2 which also complains about the static_assert. My local example was also little more complicated. I added a pair definition at the end, which makes clang 3.6 live also fail unexpectly: https://goo.gl/OKsuIU – Gene Oct 18 '15 at 20:31
  • @Gene `Pair2` is a breaker, yes. sfinae is too late. If anyone has succesfully implemented an [EqualityComparable concept] (http://en.cppreference.com/w/cpp/concept/EqualityComparable) they must have cracked this, but evidently I haven't. – Mike Kinghan Oct 19 '15 at 09:06
  • Thanks for your help. One more question: why is Pair2 a breaker? Is it because it's compare function is implemented as a non-member function? – Gene Oct 19 '15 at 11:28
  • @Gene Sort of, in the sense that we don't need to sfinae on a decltype to check at least for the *existence* of a member function, but if an equality is symmetric it must have a global definition, and then there's builtin in types, so probing `x == y` is really the only runner. The problem is that `std::pair` by definition is bound to match the "true" sfinae option - the operator is defined - but after that's committed, if X or Y don't deliver the definition of the operator, the compiler barfs at once, and this is just one instance of a general possibility with template types. – Mike Kinghan Oct 19 '15 at 17:17
  • 1
    Thanks for this answer. It works with GCC 4.9.3 at least. I have found only one issue here: any lambda will pass this check as comparable, but comparison of two lambdas does not make any sense. – scrutari Sep 30 '16 at 12:40
  • This doesn't correctly check `std::map` comparing to itself. – vvv444 May 04 '23 at 09:14