Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check whether operator== exists?

Tags:

c++

sfinae

I am trying to create an example, which would check the existence of the operator== (member or, non-member function). To check whether a class has a member operator== is easy, but how to check whether it has a non-member operator==?

This is what I have to far :

#include <iostream>

struct A
{
    int  a;

    #if 0
    bool operator==( const A& rhs ) const
    {
        return ( a==rhs.a);
    }
    #endif
};
#if 1
bool operator==( const A &l,const A &r )
{
    return ( l.a==r.a);
}
#endif


template < typename T >
struct opEqualExists
{
    struct yes{ char a[1]; };
    struct no { char a[2]; };

    template <typename C> static yes test( typeof(&C::operator==) );
    //template <typename C> static yes test( ???? );
    template <typename C> static no test(...);

    enum { value = (sizeof(test<T>(0)) == sizeof(yes)) };
};

int main()
{
    std::cout<<(int)opEqualExists<A>::value<<std::endl;
}

Is it possible to write a test function to test the existence of non-member operator==? If yes, how?

btw I have checked similar questions, but haven't found a proper solution :
Is it possible to use SFINAE/templates to check if an operator exists?

This is what I tried :

template <typename C> static yes test( const C*,bool(*)(const C&,constC&) = &operator== );

but the compilation fails if the non-member operator== is removed

like image 774
BЈовић Avatar asked Jun 30 '11 11:06

BЈовић


3 Answers

C++03

The following trick works and it can be used for all such operators:

namespace CHECK
{
  class No { bool b[2]; };
  template<typename T, typename Arg> No operator== (const T&, const Arg&);

  bool Check (...);
  No& Check (const No&);

  template <typename T, typename Arg = T>
  struct EqualExists
  {
    enum { value = (sizeof(Check(*(T*)(0) == *(Arg*)(0))) != sizeof(No)) };
  };  
}

Usage:

CHECK::EqualExists<A>::value;

The 2nd template typename Arg is useful for some special cases like A::operator==(short), where it's not similar to class itself. In such cases the usage is:

CHECK::EqualExists<A, short>::value
//                    ^^^^^ argument of `operator==`

Demo.


C++11

We need not use sizeof and null reference trick when we have decltype and std::declval

namespace CHECK
{
  struct No {}; 
  template<typename T, typename Arg> No operator== (const T&, const Arg&);

  template<typename T, typename Arg = T>
  struct EqualExists
  {
    enum { value = !std::is_same<decltype(std::declval<T>() < std::declval<Arg>()), No>::value };
  };  
}

Demo

like image 155
iammilind Avatar answered Oct 21 '22 11:10

iammilind


Have a look at Boost's Concept Check Library (BCCL) http://www.boost.org/doc/libs/1_46_1/libs/concept_check/concept_check.htm.

It enables you to write requirements that a class must match in order for the program to compile. You're relatively free with what you can check. For example, verifying the presence of operator== of a class Foo would write as follow:

#include <boost/concept_check.hpp>


template <class T>
struct opEqualExists;

class Foo {
public:
    bool operator==(const Foo& f) {
       return true;
    }

   bool operator!=(const Foo& f) {
      return !(*this == f);
   }

   // friend bool operator==(const Foo&, const Foo&);
   // friend bool operator!=(const Foo&, const Foo&);
};

template <class T>
struct opEqualExists {
   T a;
   T b;

   // concept requirements  
   BOOST_CONCEPT_USAGE(opEqualExists) {
      a == b;
   }
};


/*
bool operator==(const Foo& a, const Foo& b) {
   return true; // or whatever
}
*/


/*
bool operator!=(const Foo& a, const Foo& b) {
   return ! (a == b); // or whatever
}
*/


int main() {
   // no need to declare foo for interface to be checked

   // declare that class Foo models the opEqualExists concept
   //   BOOST_CONCEPT_ASSERT((opEqualExists<Foo>));
   BOOST_CONCEPT_ASSERT((boost::EqualityComparable<Foo>)); // need operator!= too
}

This code compiles fine as long as one of the two implementations of operator== is available.

Following @Matthieu M. and @Luc Touraille advice, I updated the code snippet to provide an example of boost::EqualityComparable usage. Once again, please note that EqualityComparable forces you to declare operator!= too.

like image 37
jopasserat Avatar answered Oct 21 '22 13:10

jopasserat


It's also possible to use only c++11 type traits to check the existence of the member:

#include <type_traits>
#include <utility>

template<class T, class EqualTo>
struct has_operator_equal_impl
{
    template<class U, class V>
    static auto test(U*) -> decltype(std::declval<U>() == std::declval<V>());
    template<typename, typename>
    static auto test(...) -> std::false_type;

    using type = typename std::is_same<bool, decltype(test<T, EqualTo>(0))>::type;
};

template<class T, class EqualTo = T>
struct has_operator_equal : has_operator_equal_impl<T, EqualTo>::type {};

You can use the trait like so:

bool test = has_operator_equal<MyClass>::value;

The resulting type of has_operator_equal will either be std::true_type or std::false_type (because it inherits from an alias of std::is_same::type), and both define a static value member which is a boolean.


If you want to be able to test whether your class defines operator==(someOtherType), you can set the second template argument:

bool test = has_operator_equal<MyClass, long>::value;

where the template parameter MyClass is still the class that you are testing for the presence of operator==, and long is the type you want to be able to compare to, e.g. to test that MyClass has operator==(long).

if EqualTo (like it was in the first example) is left unspecified, it will default to T, result in the normal definition of operator==(MyClass).

Note of caution: This trait in the case of operator==(long) will be true for long, or any value implicitly convertible to long, e.g. double, int, etc.


You can also define checks for other operators and functions, just by replacing what's inside the decltype. To check for !=, simply replace

static auto test(U*) -> decltype(std::declval<U>() == std::declval<V>());

with

static auto test(U*) -> decltype(std::declval<U>() != std::declval<V>());
like image 14
Nicolas Holthaus Avatar answered Oct 21 '22 11:10

Nicolas Holthaus