如何在模板SFINAE中避免此句子为假? [英] How to avoid this sentence is false in a template SFINAE?

查看:53
本文介绍了如何在模板SFINAE中避免此句子为假?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

So I want to write an automatic !=:

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

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

but that is impolite1. So I write

// 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的类型".

which is a type traits class that says "is t == u valid code that returns a type convertible to 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) );
}

,现在,只有==存在,它才是有效的替代.可悲的是,这有点贪婪:

and now it only is a valid override if == exists. Sadly, it is a bit greedy:

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

,因为它将几乎每个test() != test()都拥塞,而不是上面的!=被调用.我认为这是不希望的-我宁愿调用一个显式的!=而不是自动转发到==并求反.

as it will snarf up pretty much every test() != test() rather than the above != being called. I think this is not desired -- I would rather call an explicit != than auto-forward to == and negate.

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

So, I write up this traits class:

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

测试T != U是否有效.

然后我们按如下所示增加!=:

We then augment the != as follows:

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!=TU之间,而operator!=TU之间不存在.

which, if you parse it, says "this sentence is false" -- operator!= exists between T and U iff operator!= does not exist between T and U.

不足为奇的是,我给每个编译器测试过段错误的时候都这样做. (clang 3.2,gcc 4.8 4.7.2 intel 13.0.1). 我怀疑我在做的事情是非法的,但是我很想看看标准参考.(我在做的事情是违法的,因为它会导致无限制的递归模板扩展,从而确定如果我的!=适用,则要求我们检查我的!=是否适用.注释中链接的版本,用#if 1会出现明显的错误.

Not surprisingly, every compiler I have tested segfaults when fed this. (clang 3.2, gcc 4.8 4.7.2 intel 13.0.1). I suspect that what I'm doing is illegal, but I would love to see the standard reference. (edit: What I'm doing is illegal, because it induces an unbounded recursive template expansion, as determining if my != applies requires that we check if my != applies. The version linked in the comments, with #if 1, gives a sensible error).

但是我的问题是:在确定是否应该失败或以某种方式摆脱自指问题时,我是否可以说服基于SFINAE的替代忽略自身"?还是将我的operator!=的优先级降低得足够低,以至于任何明确的!=都可以胜出,即使匹配不那么好?

But my question: is there a way I can convince my SFINAE based override to ignore "itself" when deciding if it should fail or not, or somehow get rid of the self referential issue somehow? Or lower the precedence of my operator!= low enough so any explicit != wins out, even if it is otherwise not as good a match?

不检查"!=不存在"的代码效果很好,但不足以让我像将其注入到全局名称空间中一样不礼貌.

The one that doesn't check for "!= does not exist" works reasonably well, but not well enough for me to be as impolite as to inject it into the global namespace.

目标是在引入我的魔术" !=之后无需我的魔术" !=即可编译的任何代码.当且仅当!=无效,并且 bool r = !(a==b)格式正确时,我的魔术师" !=才应加入.

The goal is any code that would compile without my "magic" != does exactly the same thing once my "magic" != is introduced. If and only if != is otherwise invalid and bool r = !(a==b) is well formed should my "magic" != kick in.

脚注 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;.我认为这两种做法都是不礼貌的.

Footnote 1: If you create a template<typename U, typename T> bool operator!=(U&& u, T&& t), SFINAE will think that every pair of types has a valid != between them. Then when you try to actually call !=, it is instantiated, and fails to compile. On top of that, you stomp on bool operator!=( const foo&, const foo& ) functions, because you are a better match for foo() != foo() and foo a, b; a != b;. I consider doing both of these impolite.

推荐答案

您的方法存在的问题似乎是operator !=的后备全局定义太吸引人了,您需要SFINAE检查才能将其排除.但是,SFINAE检查取决于函数本身是否适合进行重载解析,从而导致在类型推导期间进行(尝试)无限递归.

The problem with your approach seems to be that the fallback global definition of operator != is too attractive, and you need a SFINAE check to rule it out. However, the SFINAE check depends on the eligibility of the function itself for overload resolution, thus leading to an (attempted) infinite recursion during type deduction.

在我看来,任何基于SFINAE的类似尝试都将撞到同一堵墙,所以我认为最理智的方法是使您的operator !=首先对重载解决方案的吸引力降低,然后让其他operator !=的合理写法(此刻会很清楚)会优先于此.

It seems to me that any similar attempt based on SFINAE would crash against the same wall, so the most sane approach is in my opinion to make your operator != a bit less appealing for overload resolution in the first place, and let other, reasonably written (this will be clear in a moment) overloads of operator != take precedence.

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

Given the type trait can_equal you provided:

#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 !=:

I would define the fallback operator != this way:

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时才会选择它.

As far as I know, any overload of operator != that will define exactly two function parameters (so no argument pack) will be a better fit for overload resolution. Therefore, the above, fallback version of operator != will be picked only when no better overload exist. Moreover, it will be picked only if the can_equal<> type trait will return true.

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

I've tested this against the SSCCE you prepared, where four structs are defined together with some overloads of 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()中添加了打印输出:

To verify that the desired output is produced and mirror what you did in your original version of the fallback operator !=, I added a printout to 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));
}

这是您的示例中的三个测试:

Here are the three tests from your example:

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

关于第一个测试,为类型test定义了operator !=,因此应打印行#1:

Concerning the first test, operator != is defined for type test, so line #1 should print:

(!==)1

关于第二个测试,operator !=未为test3定义 ,并且test3无法转换为test4,因此我们的全局operator !=应该起作用并取反operator ==的重载需要两个const test3&的结果.因此,#2行应打印:

Regarding the second test, operator != is not defined for test3, and test3 is not convertible to test4, so our global operator != should come into play and negate the result of the overload of operator == that takes two const test3&. Therefore, line #2 should print:

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

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

Finally, the third test involves two rvalue objects of type test4, for which operator != is defined (because the arguments are convertible to test4 const&). Therefore, line #3 should print:

(!=)1

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

And here is a live example showing that the output produced is the expected one.

这篇关于如何在模板SFINAE中避免此句子为假?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆