27

所以我想写一个自动的!=

template<typename U, typename T>
bool operator!=(U&& u, T&& t) {
  return !( std::forward<U>(u) == std::forward<T>(t) );
}

但这是不礼貌的1。所以我写

// T() == U() is valid?
template<typename T, typename U, typename=void>
struct can_equal:std::false_type {};

template<typename T, typename U>
struct can_equal<
   T,
   U,
   typename std::enable_if<
      std::is_convertible<
         decltype( std::declval<T>() == std::declval<U>() ),
         bool
      >::value
   >::type
>: std::true_type {};

这是一个类型特征类,它说“是t == u返回可转换为的类型的有效代码bool”。

所以我改进了我的!=

template<typename U, typename T,
  typename=typename std::enable_if<can_equal<T,U>::value>::type
>
bool operator!=(U&& u, T&& t) {
  return !( std::forward<U>(u) == std::forward<T>(t) );
}

现在它只是一个有效的覆盖(如果==存在)。可悲的是,它有点贪婪:

struct test {
};
bool operator==(const test&, const test&);
bool operator!=(const test&, const test&);

因为它会捕获几乎所有test() != test()而不是上面!=的调用。我认为这是不希望的——我宁愿调用显式而!=不是自动转发==和否定。

所以,我写了这个特质类:

template<typename T, typename U,typename=void>
struct can_not_equal // ... basically the same as can_equal, omitted

哪个测试是否T != U有效。

然后我们增加!=如下:

template<typename U, typename T,
  typename=typename std::enable_if<
    can_equal<T,U>::value
    && !can_not_equal<T,U>::value
  >::type
>
bool operator!=(U&& u, T&& t) {
  return !( std::forward<U>(u) == std::forward<T>(t) );
}

如果你解析它,它会说“这句话是假的”——operator!=存在于 and 之间TU当且仅当operator!=且不存在于Tand之间U

毫不奇怪,我测试过的每个编译器都会在输入这个时出现段错误。(clang 3.2,gcc 4.8 4.7.2 英特尔 13.0.1)。 我怀疑我正在做的事情是非法的,但我很想看到标准参考。(编辑:我所做的是非法的,因为它会导致无限递归模板扩展,因为确定我的!=应用是否需要我们检查我的!=应用。评论中链接的版本带有#if 1,给出了一个合理的错误)。

但我的问题是:有没有一种方法可以说服我基于 SFINAE 的覆盖在决定它是否应该失败时忽略“自身”,或者以某种方式摆脱自我引用问题?或者降低我的优先级operator!=足够低以便任何显式!=获胜,即使它不是那么好的匹配?

不检查“!=不存在”的那个工作得相当好,但还不足以让我像将其注入全局命名空间那样不礼貌。

目标是,一旦我的“魔法”被引入,任何可以在没有我的“魔法”的情况下编译的代码都会!=做同样的事情。!=当且仅当!=否则无效并且 bool r = !(a==b)格式正确时,我的“魔术”才!=起作用。


脚注1:如果你创建一个template<typename U, typename T> bool operator!=(U&& u, T&& t),SFINAE 会认为每一对类型!=在它们之间都有一个有效的。然后,当您尝试实际调用时!=,它会被实例化,并且无法编译。最重要的是,你踩着bool operator!=( const foo&, const foo& )函数,因为你更适合foo() != foo()and foo a, b; a != b;。我认为这两种做法都是不礼貌的。

4

1 回答 1

13

您的方法的问题似乎是后备全局定义operator !=太有吸引力,您需要 SFINAE 检查以排除它。但是,SFINAE 检查取决于函数本身是否适合重载决议,因此在类型推导期间会导致(尝试的)无限递归。

在我看来,任何基于 SFINAE 的类似尝试都会撞到同一堵墙,所以我认为最理智的方法是首先让你operator !=对重载解决方案的吸引力降低一点,并让其他合理编写的方法(这个稍后会清楚)重载operator !=优先。

鉴于can_equal您提供的类型特征:

#include <type_traits>
#include <functional>

template<typename T, typename U, typename=void>
struct can_equal : std::false_type {};

template<typename T, typename U>
struct can_equal<
   T,
   U,
   typename std::enable_if<
      std::is_convertible<
         decltype( std::declval<T>() == std::declval<U>() ),
         bool
      >::value
   >::type
>: std::true_type {};

我会这样定义后备operator !=

template<typename T, typename U>
bool is_not_equal(T&& t, U&& u)
{
    return !(std::forward<T>(t) == std::forward<U>(u));
}

template<
    typename T,
    typename... Ts,
    typename std::enable_if<can_equal<T, Ts...>::value>::type* = nullptr
    >
bool operator != (T const& t, Ts const&... args)
{
    return is_not_equal(t, args...);
}

据我所知,任何重载operator !=都将精确定义两个函数参数(因此没有参数包)将更适合重载解析。operator !=因此,只有在不存在更好的重载时才会选择上述的后备版本。此外,仅当can_equal<>类型特征返回时才会选择它true

我已经针对您准备的 SSCCE 对此进行了测试,其中struct定义了四个 s 以及一些重载的operator ==and operator !=

struct test { };

bool operator==(const test&, const test&) { std::cout << "(==)"; return true; }
bool operator!=(const test&, const test&) { std::cout << "(!==)"; return true; }

struct test2 { };

struct test3 { };
bool operator == (const test3&, const test3&) 
{ std::cout << "(==)"; return true; }

struct test4 { };

template<typename T, 
         EnableIf< std::is_convertible< T, test4 const& >::value >... >
bool operator == ( T&&, T&& ) { std::cout << "(==)"; return true; }

template<typename T, 
         EnableIf< std::is_convertible< T, test4 const& >::value >... >
bool operator != ( T&&, T&& ) { std::cout << "(!=)"; return true; }

为了验证是否生成了所需的输出并反映您在原始版本的回退中所做的事情operator !=,我添加了一个打印输出到is_not_equal()

template<typename T, typename U>
bool is_not_equal(T&& t, U&& u)
{
    std::cout << "!"; // <== FOR TESTING PURPOSES
    return !(std::forward<T>(t) == std::forward<U>(u));
}

以下是您示例中的三个测试:

std::cout << (a != b) << "\n"; // #1
std::cout << (test3() != test3()) << "\n"; // #2
std::cout << (test4() != test4()) << "\n"; // #3

关于第一个测试,operator !=是为 type 定义的test,所以#1应该打印:

(!==)1

关于第二个测试,没有operator !=为定义,也不能转换为,所以我们的全局应该发挥作用并否定需要两个的重载的结果。因此,行应该打印:test3test3test4operator !=operator ==const test3&#2

!(==)0 // operator == returns true, and is_not_equal() negates it

最后,第三个测试涉及两个类型为 的右值对象test4,为其operator !=定义(因为参数可转换为test4 const&)。因此,行#3应该打印:

(!=)1

这是一个现场示例,显示产生的输出是预期的。

于 2013-04-09T23:18:20.150 回答