在模板中比较 == !=

Comparing == != in templates

本文关键字:比较      更新时间:2023-10-16

在模板类中执行 == 和 != 运算符的正确方法是什么?假设以下代码:

template<typename T>
class C {
        T x, y;
        public:
        C(T a, T b) : x(a), y(b) {}
        bool cmp() {
                return x == y;
        }
};
int main()
{
        // OK
        C<int> i(1,2);
        i.cmp();
        // not OK
        C<double> d(1.0, 2.0);
        d.cmp();
        return 0;
}

如果你用 g++ -Wfloat-equal 构建它,你会得到

警告:将浮点数与 == 或 != 进行比较是不安全的 [-浮点等于]

因为你不能简单地比较浮点变量。


更新

我已经使用这样的type_traits和enable_if解决了这个问题(感谢@Andrew和@OMGtechy):

#include <type_traits>
#include <limits>
#include <cmath>
#include <iostream>
using namespace std;
template <typename IntegralType>
typename std::enable_if<std::is_integral<IntegralType>::value, bool>::type
equal(const IntegralType& a, const IntegralType& b) {
        return a == b;
}
template <typename FloatingType>
typename std::enable_if<std::is_floating_point<FloatingType>::value, bool>::type
equal(const FloatingType& a, const FloatingType& b) {
        return std::fabs(a-b) < std::numeric_limits<FloatingType>::epsilon();
}
template<typename T>
class C {
    T x, y;
    public:
    C(T a, T b) : x(a), y(b) {}
    bool cmp() {
        return equal(x, y);
    }
};
int main()
{
    // OK
    C<int> i(1,2);
    cout << i.cmp() << endl;
    // not OK
    C<double> d(1.0, 1.0);
    cout << d.cmp() << endl;
    return 0;
}

这个问题似乎在问两件事:

  1. 如何在不使用运算符==的情况下进行浮点比较,以及
  2. 如何根据传递给模板的类型修改模板的行为。

第二个问题的一个答案是使用类型特征。下面的代码针对您的情况演示了这一点,为常规类型(使用 ==)提供了comparison_traits,并使用公差(也回答了第一个问题)为双精度提供了专用化。

#include <cmath>
template <typename T> struct comparison_traits {
  bool equal(const T& a, const T& b) {
    return a == b;
  }
  // etc.
};
template<> struct comparison_traits<double> {
  bool equal(const double& a, const double& b) {
    return fabs(a - b) < 1e-15; // or whatever...
  }
};
template <typename T>
class C {
  T x, y;
  public:
    C(const T& a, const T& b) : x(a), y(b) {}
    bool cmp() {
      return comparison_traits<T>::equal(x, y);
    }
};
int main() {
  // OK
  C<int> i(1, 2);
  i.cmp();
  // Now OK too...
  C<double> d(1.0, 2.0);
  d.cmp();
  return 0;
}

其他选项包括:

  • 提供一个模板参数,允许您指定比较函数,默认为 std::equal_to

  • 模板专用化为 double,以便您可以编写不同的 cmp() 实现

这取决于它将如何使用。正确比较浮点数取决于上下文。

我会推荐@Niall所说的:添加一个比较器模板参数,默认为 std::equal_to .这将允许调用方控制如何比较值。例如,请参阅有关std::sort的文档,以了解比较器参数的使用方式。这样做的缺点是,调用方有责任考虑比较float。如果他们忘记了,那么他们会收到您看到的编译器警告。

另一种选择是模板类专用化。为您的班级专门处理floatdouble类型,并使用您喜欢的任何逻辑以不同的方式比较它们。可能不是最好的解决方案。这样做的好处是,调用方不再需要记住指定比较器。

如果你问为什么收到这个警告:

这里有一些例子:

double a,b;
a=10.0/13.0;
b = a/3;
b*=3;
std::cout<<"a="<<a<<std::endl;
std::cout<<"b="<<b<<std::endl;
if(a!=b){
    std::cout<<"NOT  equal!!"<<std::endl;
    std::cout<<"a-b="<<a-b<<std::endl;
}
else
    std::cout<<"equal"<<std::endl;

如果你会做数学,A和B显然是相等的。但这是我得到的输出:

a=0.769231
b=0.769231
NOT  equal!!
a-b=-1.11022e-016

因为它不是那么准确,所以对 double 的正确比较应该定义一些容忍度:

例如(容忍度可能会根据需要而改变):

int compare(double a, double b)
{
    double tolerancy = 0.000001;
    if (abs(a-b) < tolerancy) return 0;
    else if (a>b) return 1;
    else /*if (a<b)*/ return -1;
} 

如果我使用这个比较,我会得到:

a=0.769231
b=0.769231
equal