SFINAE 没有正确禁用不明确的重载

SFINAE does not properly disable ambiguous overloads

我正在尝试构建一个更灵活的 std::equal_to 版本,它可以在两种不同类型的对象上调用 T1T2 并且只需要 T1::operator==(T2)T2::operator==(T1)定义。我正在使用 C++17。首先,我有一些结构可以测试是否定义了 SomeType::operator==(OtherType)

#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 {};

我认为这行得通 - 至少,在下面用 truefalse 替换 EqualityOperatorDefined 的用法不会使问题消失。实际灵活的equal_to是这样的:

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;
}

你可以找到完整的代码here at godbolt

但是,GCC 和 Clang 都抱怨这里的错误重载。 GCC 告诉我:

<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)
      |                 ^~~~~~~~

std::enable_if中的两个布尔表达式只能有一个为真。因此,operator() 的另一个(在这种情况下:第二个)定义应该被 SFINAE 去掉,并且不应该有重载问题。

我想我避免了应用 SFINAE 的常见陷阱。两个函数模板都依赖于它们的模板参数。

这里出了什么问题?

operator== 的 SFINAE 检测器有问题。替换为:

decltype(std::declval<FromT>() == std::declval<ToT>())

有了这个:

decltype(std::declval<FromT>() == std::declval<ToT>(), void())