Comparing == != in templates
Asked Answered
T

3

5

What is the correct way to perform == and != operators in template classes? Assume this code:

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

If you build it with g++ -Wfloat-equal, you'll get

warning: comparing floating point with == or != is unsafe [-Wfloat-equal]

because you can't simply compare float variables.


Update

I've solved the problem using type_traits and enable_if like this (thanks @Andrew and @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;
}
Transept answered 1/12, 2014 at 12:50 Comment(10)
If you are concerned about the type not having an appropriate operator==, consider added a argument to the template that is the comparator to be used (and then have the default be std::equal_to)Shiah
This has a lot to do with comparing floating point values, but virtually nothing to do with templates.Leacock
What is correct depends on the use case. What do you want to happen for floating point here? For some cases, == is perfectly fine with floating point. For others, checking for near values is correct.Quintile
@Leacock Actually, I think it's a good question. How to implement this template so it handles all types including floating point values?Cabrera
@JosephMansfield The code given handles all types already.Quintile
@JosephMansfield Oh, OK then. But I didn't mean to say it was a bad question.Leacock
@Quintile I'm aware of that, but the asker wants to know how you would implement a template that is capable of handling the fact that some people might want a "safer" form of comparison for floating point values. There are a couple of approaches to this.Cabrera
There was a similar question recently.Marriage
"you can't simply compare float variables" - yes you can. You just can't rely on mathematically equivalent calculations giving exactly equal results.Conoid
#588504Moneylender
A
7

This question seems to be asking two things:

  1. How can I do floating point comparisons without using operator==, and
  2. how can I modify the behaviour of a template depending on the type passed to it.

One answer to the second question is to use type traits. The code below demonstrates this for your situation, providing a comparison_traits for general types (using ==) and a specialisation for doubles, using a tolerance (which answers the first question, too).

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

Other options include:

  • Providing a template parameter that allows you to specify a comparison function, defaulting to std::equal_to

  • Specialising your template for double, so that you can write a different implementation of cmp()

Aboutship answered 1/12, 2014 at 13:3 Comment(2)
You could use type traits to do it for all floating point types rather than specialising for double and float seperatelyBalaton
True. I wanted to stick fairly close to the example in the question, though. Also, it doesn't hurt to leave some things as an exercise for the reader :)Aboutship
S
2

It depends how it will be used. Comparing floats properly depends on the context.

I would recommend what @Niall says: add a comparator template parameter, defaulting to std::equal_to. This will allow callers to control how values are compared. See for example the docs on std::sort to see how a comparator parameter is used. The downside to this is that it's the caller's responsibility to account for comparing floats. If they forget, then they'll get the compiler warning you see.

Another option is template class specialization. Make a specialization for your class to deal specifically with float or double types, and compare them differently using whatever logic you prefer. Probably not the best solution though. The benefit to this is that callers no longer need to remember to specify a comparator.

Scottiescottish answered 1/12, 2014 at 12:59 Comment(0)
G
0

If you ask why you get this warning:

here some example:

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;

if you'll do the math a and b are clearly equal. but this is the output I've got:

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

because it is not so accurate, a proper comparison for double should define some tolerancy:

for example(tolerancy may change according needs):

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

and if I use this compare i get:

a=0.769231
b=0.769231
equal
Germayne answered 1/12, 2014 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.