在SFINAE模板中如何避免这句话是假的?

Yak*_*ont 26 c++ templates operator-overloading sfinae c++11

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

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

但这是不礼貌的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 {};
Run Code Online (Sandbox Code Playgroud)

这是一个类型traits类,它表示"是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) );
}
Run Code Online (Sandbox Code Playgroud)

现在它只是一个有效的覆盖,如果==存在.可悲的是,它有点贪心:

struct test {
};
bool operator==(const test&, const test&);
bool operator!=(const test&, const test&);
Run Code Online (Sandbox Code Playgroud)

因为它几乎每个人都会咆哮,test() != test()而不是上面!=所说的.我认为这不是我们想要的 - 我宁愿打电话给明确而!=不是自动转发==和否定.

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

template<typename T, typename U,typename=void>
struct can_not_equal // ... basically the same as can_equal, omitted
Run Code Online (Sandbox Code Playgroud)

哪些测试是否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) );
}
Run Code Online (Sandbox Code Playgroud)

其中,如果你分析它,说:"这句话是假的" - operator!=之间存在T以及UIFF operator!=之间不存在TU.

毫不奇怪,每个编译器在测试时都会测试段错误.(clang 3.2,gcc 4.8 4.7.2 intel 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()foo a, b; a != b;.我认为这两种不礼貌.

And*_*owl 12

您的方法的问题似乎是回退全局定义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 {};
Run Code Online (Sandbox Code Playgroud)

我会这样定义回退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...);
}
Run Code Online (Sandbox Code Playgroud)

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

我已经针对你准备的SSCCE进行了测试,其中四个structs被定义,并且有一些重载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; }
Run Code Online (Sandbox Code Playgroud)

为了验证是否产生了所需的输出并镜像了您在原始版本的后备中所做的操作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));
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

(!==)1
Run Code Online (Sandbox Code Playgroud)

关于第二个测试,operator !=不是为定义test3,并test3没有转换为test4,所以我们的全球operator !=应发挥作用,否定的过载的结果operator ==可以接受两个const test3&.因此,行#2应打印:

!(==)0 // operator == returns true, and is_not_equal() negates it
Run Code Online (Sandbox Code Playgroud)

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

(!=)1
Run Code Online (Sandbox Code Playgroud)

这是一个实例,显示产生的输出是预期的输出.