所以我想写一个自动的!=
:
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/