c++ - 如何避免这句话在模板 SFINAE 中是错误的?

标签 c++ templates c++11 operator-overloading sfinae

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

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!=存在于 T 之间和 U如果operator!= T 之间不存在和 U .

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

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

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

目标是任何可以在没有我的“魔法”的情况下编译的代码!=一旦我的“魔法”!= 做同样的事情介绍。当且仅当 !=否则无效 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()foo a, b; a != b; .我认为这两种做法都是不礼貌的。

最佳答案

您的方法的问题似乎是 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 == 的一些重载一起定义。和 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; }

验证是否生成了所需的输出并反射(reflect)您在原始版本的后备 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 !=为类型 test 定义, 所以行 #1应该打印:

(!==)1

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

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

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

(!=)1

这里是 live example表明产生的输出是预期的。

关于c++ - 如何避免这句话在模板 SFINAE 中是错误的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15912283/

相关文章:

c++ - 缓存大量回调,然后在没有 v-table 成本的情况下批量调用它们

c++ - 为什么 std::begin() 和 std::end() 适用于固定数组,而不适用于动态数组?

c++ - 带 -std=c++11 的 GCC 看不到 C++ 头文件(通过 PyDSTool)

c++ - 无法声明静态 constexpr char []

c++ - 为什么 C++ 右值不是不可变的

c++ - 我可以获得最后调用的 CUDA API 函数的名称吗?

templates - 为 APIGateway 方法设置缓存

templates - 模板文本正文中不允许 JSF 和 EJB #{...}

c++ - 编译时检查是模板类型还是 vector

c++ - Code::Blocks 出现 GLUT 编译错误