3

I need to specialise one of my templates and would like to do it depending on the available operator.

I basically need to know if I can do Foo == Bar

template<class T >
std::enable_if_t<has_equalOperator<T>::value> Compare( const T& other )
{
  // can compare using ==
  ...
} 

template<class T >
std::enable_if_t<!has_equalOperator<T>::value> Compare( const T& other )
{
  // cannot compare use some other method
  ...
} 

Does the standard have something similar? If not, how could I implement it?

Simon Goodman
  • 1,174
  • 1
  • 8
  • 35

2 Answers2

3

This kind of traits type almost always takes the following form:

#include <utility>
#include <iostream>
#include <string>

template<class T, class Arg>
  struct has_equals_impl
  {
    template<class U> static 
      auto test(const U* p) 
      -> decltype( /* the test is here */
                  (*p) == std::declval<Arg>(), 
                   /* end of test */
                  void(), std::true_type());

    static auto test(...) -> std::false_type;

    using type = decltype(test((const T*)nullptr));

  };

// this typedef ensures that the actual type is either std::true_type or std::false_type
template<class T, class Arg> using has_equals = typename has_equals_impl<T, Arg>::type;

int main()
{
  // int == int? yes
  std::cout << has_equals<int, int>() << std::endl;

  // string == int? no
  std::cout << has_equals<std::string, int>() << std::endl;
}

Here is an almost-complete suite of binary operator checkers with some tests.

You should get the general idea. Note that I had to create left_shift and right_shift function objects since these don't exist in the standard library already.

#include <utility>
#include <iostream>
#include <string>
#include <algorithm>
#include <cassert>


template<class X, class Y, class Op>
struct op_valid_impl
{
    template<class U, class L, class R>
    static auto test(int) -> decltype(std::declval<U>()(std::declval<L>(), std::declval<R>()),
                                      void(), std::true_type());

    template<class U, class L, class R>
    static auto test(...) -> std::false_type;

    using type = decltype(test<Op, X, Y>(0));

};

template<class X, class Y, class Op> using op_valid = typename op_valid_impl<X, Y, Op>::type;

namespace notstd {

    struct left_shift {

        template <class L, class R>
        constexpr auto operator()(L&& l, R&& r) const
        noexcept(noexcept(std::forward<L>(l) << std::forward<R>(r)))
        -> decltype(std::forward<L>(l) << std::forward<R>(r))
        {
            return std::forward<L>(l) << std::forward<R>(r);
        }
    };

    struct right_shift {

        template <class L, class R>
        constexpr auto operator()(L&& l, R&& r) const
        noexcept(noexcept(std::forward<L>(l) >> std::forward<R>(r)))
        -> decltype(std::forward<L>(l) >> std::forward<R>(r))
        {
            return std::forward<L>(l) >> std::forward<R>(r);
        }
    };

}

template<class X, class Y> using has_equality = op_valid<X, Y, std::equal_to<>>;
template<class X, class Y> using has_inequality = op_valid<X, Y, std::not_equal_to<>>;
template<class X, class Y> using has_less_than = op_valid<X, Y, std::less<>>;
template<class X, class Y> using has_less_equal = op_valid<X, Y, std::less_equal<>>;
template<class X, class Y> using has_greater_than = op_valid<X, Y, std::greater<>>;
template<class X, class Y> using has_greater_equal = op_valid<X, Y, std::greater_equal<>>;
template<class X, class Y> using has_bit_xor = op_valid<X, Y, std::bit_xor<>>;
template<class X, class Y> using has_bit_or = op_valid<X, Y, std::bit_or<>>;
template<class X, class Y> using has_left_shift = op_valid<X, Y, notstd::left_shift>;
template<class X, class Y> using has_right_shift = op_valid<X, Y, notstd::right_shift>;

int main()
{
    assert(( has_equality<int, int>() ));
    assert((not has_equality<std::string&, int const&>()()));
    assert((has_equality<std::string&, std::string const&>()()));
    assert(( has_inequality<int, int>() ));
    assert(( has_less_than<int, int>() ));
    assert(( has_greater_than<int, int>() ));
    assert(( has_left_shift<std::ostream&, int>() ));
    assert(( has_left_shift<std::ostream&, int&>() ));
    assert(( has_left_shift<std::ostream&, int const&>() ));

    assert((not has_right_shift<std::istream&, int>()()));
    assert((has_right_shift<std::istream&, int&>()()));
    assert((not has_right_shift<std::istream&, int const&>()()));
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
2

In C++17, it can be simplified with std::is_detected:

typename <typename LHS, typename RHS>
using equal_t = decltype(std::declval<LHS>() == std::declval<RHS>());

template <typename T>
using has_equal = std::is_detected<equal_t, T, T>;

Demo

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Jarod42
  • 203,559
  • 14
  • 181
  • 302