I'm trying to build a more flexible version of std::equal_to
, which can be called on objects of two different types T1
and T2
and only requires that T1::operator==(T2)
or T2::operator==(T1)
is defined. I'm using C++17. First, I have some struct that tests if SomeType::operator==(OtherType)
is defined:
#include <vector>
#include <utility>
#include <functional>
template<class FromT, class ToT, class Dummy = void>
struct EqualityOperatorDefined : std::false_type {};
template<class FromT, class ToT>
struct EqualityOperatorDefined <FromT, ToT, decltype(std::declval<FromT>() == std::declval<ToT>())>
: std::true_type {};
I think that this works - at least, replacing the usage of EqualityOperatorDefined
with true
or false
in the following does not make the problem go away. The actual flexible equal_to
is this:
template<class T1, class T2>
class FlexibleEqualsTo
{
private:
static constexpr bool cmpT1toT2 = EqualityOperatorDefined<T1, T2>::value;
static constexpr bool cmpT2toT1 = EqualityOperatorDefined<T2, T1>::value;
public:
template<class Dummy = T1>
constexpr bool operator()(const std::enable_if_t<cmpT1toT2, Dummy> & lhs, const T2& rhs)
{
return lhs == rhs;
}
template<class Dummy = T1>
constexpr bool operator()(const std::enable_if_t<cmpT2toT1 && !cmpT1toT2, Dummy> & lhs, const T2& rhs)
{
return rhs == lhs;
}
};
int main() {
FlexibleEqualsTo<int, int> foo;
}
You can find the complete code here at godbolt.
However, GCC and Clang both complain about erroneous overloads here. GCC tells me:
<source>: In instantiation of 'class FlexibleEqualsTo<int, int>':
<source>:32:32: required from here
<source>:25:17: error: 'template<class Dummy> constexpr bool FlexibleEqualsTo<T1, T2>::operator()(std::enable_if_t<(FlexibleEqualsTo<T1, T2>::cmpT2toT1 && (! FlexibleEqualsTo<T1, T2>::cmpT1toT2)), Dummy>&, const T2&) [with Dummy = Dummy; T1 = int; T2 = int]' cannot be overloaded with 'template<class Dummy> constexpr bool FlexibleEqualsTo<T1, T2>::operator()(std::enable_if_t<FlexibleEqualsTo<T1, T2>::cmpT1toT2, Dummy>&, const T2&) [with Dummy = Dummy; T1 = int; T2 = int]'
25 | constexpr bool operator()(const std::enable_if_t<cmpT2toT1 && !cmpT1toT2, Dummy> & lhs, const T2& rhs)
| ^~~~~~~~
<source>:19:17: note: previous declaration 'template<class Dummy> constexpr bool FlexibleEqualsTo<T1, T2>::operator()(std::enable_if_t<FlexibleEqualsTo<T1, T2>::cmpT1toT2, Dummy>&, const T2&) [with Dummy = Dummy; T1 = int; T2 = int]'
19 | constexpr bool operator()(const std::enable_if_t<cmpT1toT2, Dummy> & lhs, const T2& rhs)
| ^~~~~~~~
Only one of the two boolean expressions in std::enable_if
can be true. Thus, the other (in this case: the second) definition of operator()
should be SFINAEed away, and there should be no overloading problems.
I think I avoided the usual pitfalls of applying SFINAE. Both function templates depend on their template parameter.
What is going wrong here?
Your SFINAE detector for operator==
is bugged. Replace this:
decltype(std::declval<FromT>() == std::declval<ToT>())
with this:
decltype(std::declval<FromT>() == std::declval<ToT>(), void())