所以我想写一个自动!=
:
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
iff operator!=
在{{1}之间不存在}和T
。
毫不奇怪,每个编译器在测试时都会测试段错误。 (clang 3.2,gcc 4.8 4.7.2 intel 13.0.1)。 我怀疑我正在做的事情是非法的,但我很乐意看到标准参考。(编辑:我正在做的是非法的,因为它会导致无限的递归模板扩展,因为它确定如果我的U
适用,则需要我们检查我的!=
是否适用。评论中链接的版本!=
会产生明显的错误。)
但我的问题是:有没有办法可以说服我的SFINAE覆盖在决定它是否应该失败时忽略“本身”,或以某种方式摆脱自我指涉问题?或者将我的#if 1
的优先级降低到足以使任何明确的operator!=
获胜,即使它不是一个好的匹配?
不检查“!=
不存在”的那个工作得相当好,但不足以让我像将它注入全局命名空间一样不礼貌。
目标是任何编译而没有我的“魔法”!=
的代码在引入我的“魔法”!=
后完全相同的事情。如果且且仅当!=
无效且 !=
形成良好,我的“魔法”bool r = !(a==b)
才会启动。
脚注 1 :如果您创建!=
,SFINAE会认为每对类型之间都有一个有效的template<typename U, typename T> bool operator!=(U&& u, T&& t)
。然后,当您尝试实际调用!=
时,它将被实例化,并且无法编译。最重要的是,你踩踏!=
函数,因为你更适合bool operator!=( const foo&, const foo& )
和foo() != foo()
。我认为做这两种不礼貌。
答案 0 :(得分: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 {};
我会以这种方式定义回退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
以及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; }
要验证是否生成了所需的输出并镜像了您在原始版本的后备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
,因此我们的全球{ {1}}应该发挥作用并否定operator !=
超载的结果需要两个operator ==
。因此,行const test3&
应打印:
#2
最后,第三个测试涉及两个类型为!(==)0 // operator == returns true, and is_not_equal() negates it
的右值对象,其中定义了test4
(因为参数可转换为operator !=
)。因此,行test4 const&
应打印:
#3
这是一个live example,显示产生的输出是预期的输出。