当前位置: 首页 > 知识库问答 >
问题:

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

万嘉熙
2023-03-14

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

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 {};
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&);

因此,我编写了这个特征类:

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) );
}

脚注1:如果创建模板 bool操作符!=(U&&U,t&&t) ,SFINAE将认为每对类型之间都有一个有效的!=。然后,当您试图实际调用!=时,它被实例化,并且编译失败。在此基础上,您可以使用bool操作符!=(const foo&,const foo&)函数,因为您可以更好地匹配foo()!=foo()fooa,b;a!=b;。我认为做这两件事都是不礼貌的。

共有1个答案

阎鸿煊
2023-03-14

您的方法的问题似乎是操作符!=的回退全局定义太吸引人了,您需要进行SFINAE检查才能排除它。但是,SFINAE检查依赖于函数本身用于重载解析的资格,从而导致在类型演绎期间(尝试)无限递归。

在我看来,任何基于SFINAE的类似尝试都将遇到同样的问题,因此在我看来,最明智的方法是首先降低运算符!=对重载解决的吸引力,然后让运算符!=的其他合理编写的重载(这将在稍后清楚地说明)优先。

给定您提供的类型特征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 {};

我将这样定义回退运算符!=

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...);
}

据我所知,运算符!=的任何重载只要定义了两个函数参数(所以没有参数包)就更适合重载解析。因此,只有在没有更好的重载时,才会选择操作符!=的上述回退版本。而且,只有当can_equal<>类型特征返回true时,才会选择它。

我已经根据您准备的SSCE测试了这一点,其中定义了四个结构以及操作符==操作符!=的重载:

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; }
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

关于第一个测试,运算符!=是为类型test定义的,因此#1行应该打印:

(!==)1

关于第二个测试,运算符!=没有为test3定义,test3不能转换为test4,因此我们的全局运算符!=应该起作用,并否定运算符==的重载结果,该重载取两个常量test3&。因此,第#2行应打印:

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

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

(!=)1

这里有一个现场示例,表明所产生的输出是预期的。

 类似资料:
  • 模板中有一些HTML元素,例如模板/电子邮件/电子邮件标题。php第41行,没有内联样式: 但当它在前端渲染时,它会以许多内联样式出现: 如何以及在哪里进行这种风格的注射? 如何避免这种风格的注射? 我想在模板中编写自己的内联样式。做这件事的正确方法是什么? 我注意到,一些注入,比如上面的例子,取决于元素的ID,所以我可以删除它们的ID,但其他情况下是可变的,比如模板/电子邮件/电子邮件头。php

  • 问题内容: 我的问题是我必须在try语句中设置变量,否则会出现编译错误。 稍后我需要使用该变量,但现在超出了范围,所以我相信。我在try语句外部初始化了该变量并将其设置为null,我以为可以在外部访问它,但仍然得到了。 下面的代码,其中有很多代码使阅读变得更容易-我知道这是不好的代码,但是我是Servlets的新手,只想看看它与所有活动部件一起运行时应如何工作。 我创建了另一个类,该类调用crea

  • 如果我有一个默认模板类型的模板类,我必须编写模板尖括号。有没有可能避免这种情况? 示例: 到目前为止,我都是通过一个单独的名称空间和重新定义类来实现这一点的: 问题是,如果我想将类与其他类型一起使用,我必须检查名称空间detail_。有没有另一种解决方案,我还没有看到。

  • 需要在一个PDF文件中导出所有的表单,所以我找到了这段代码,它工作(导出一个单PDF,每个表单有一页)。但是我不想使用select/active语句,我更喜欢使用存储对象的变量。 问题:在这段代码中如何避免select/ActiveSheet?

  • 问题内容: 建议在HTML页面中使用表格(现在已经有了CSS)? 表格有什么用途?表具有哪些CSS所没有的功能? 问题答案: 一点都不。但是将表格用于表格数据。只是不要将它们用于一般布局。 但是,如果您显示表格数据(例如结果或什至是表格),请继续使用表格!

  • 本文向大家介绍如何避免MySQL查询中的OR语句过多?,包括了如何避免MySQL查询中的OR语句过多?的使用技巧和注意事项,需要的朋友参考一下 使用MySQL避免太多的OR语句。让我们首先创建一个表- 使用插入命令在表中插入一些记录- 使用select语句显示表中的所有记录- 这将产生以下输出- 以下是避免在MySQL查询中使用太多OR语句的查询,即使用- 这将产生以下输出-