1

In my code, I have a lot of template algorithms where the template type must be a floating point (float, double, or long double). Some of these algorithms require a default epsilon value. Example:

template <typename FloatType>
bool approx(FloatType x1, FloatType x2)
{
  const FloatType epsilon; // How can I set it ?
  return abs(x2 - x1) < epsilon;
}

How can I define it ? I tried the following, it's accepted by gcc, but it's not standard (and not valid in C++11). I know it's possible in C++11, but I have to be compatible with c++03.

template <typename FloatType>
struct default_epsilon
{};

template <>
struct default_epsilon<float>
{
  static const float value = 1.0e-05f;
};

template <>
struct default_epsilon<double>
{
  static const double value = 1.0e-10;
};

template <>
struct default_epsilon<long double>
{
  static const long double value = 1.0e-12l;
};

// Then, I use it like that :
template <typename FloatType>
bool approx(FloatType x1, FloatType x2)
{
  return abs(x2 - x1) < default_epsilon<FloatType>::value;
}
// or that
bool approx(FloatType x1, FloatType x2, FloatType epsilon = default_epsilon<FloatType>::value)
{
  return abs(x2 - x1) < epsilon;
}
Caduchon
  • 4,574
  • 4
  • 26
  • 67
  • 4
    Do like numeric_limits, use a (inline) function. – AProgrammer Nov 25 '19 at 12:53
  • 2
    _but it's not standard_ Why? _I have to be compatible with c++03._ Template specialization is not in C++ since C++11, it was in C++03 as well. – Daniel Langr Nov 25 '19 at 12:54
  • @AProgrammer, it's not my prefered solution due to the fact that I can't use it as default argument value in function declaration. – Caduchon Nov 25 '19 at 12:55
  • @DanielsaysreinstateMonica https://stackoverflow.com/a/9200350/3378179 Before C++11, what I wrote is only possible for integer types, not floating types. – Caduchon Nov 25 '19 at 12:57
  • 2
    @Caduchon So initialize those static constants outside of class definition. – Daniel Langr Nov 25 '19 at 12:59
  • `double` and `long double` are kinda the same type on most platforms, aren't they? – goodvibration Nov 25 '19 at 12:59
  • @goodvibration On x64, `long double` is typically implemented with 80-bit extended precision. [Live demo](https://godbolt.org/z/jRKebG). – Daniel Langr Nov 25 '19 at 13:04
  • @goodvibration : on my platform, `sizeof` on `float`, `double` and `long double` gives me 4, 8 and 16. – Caduchon Nov 25 '19 at 13:06
  • instead of storing epsilon as float, store the precision exponent (-5, -10, -12) as an integeral constant, and have your functions accept a "precision" parameter instead of epsilon. – Shloim Nov 25 '19 at 13:08
  • @DanielsaysreinstateMonica and Caduchon: Right... Well, then it also depends on the compiler (so in short - a combination of the underlying HW architecture and the designated compiler). But the fact that these two types can be identical on some platforms implies that this code will not compile in all cases. – goodvibration Nov 25 '19 at 13:09
  • @Caduchon Not C++03 expert, but you cannot use this experssion `std::numeric_limits::epsilon()` as a default function argument there? – Daniel Langr Nov 25 '19 at 13:09
  • @Caduchon: See my response above. – goodvibration Nov 25 '19 at 13:11
  • @goodvibration No, they cannot be the same. They are [guaranteed by the C++ Standard to be different types](http://eel.is/c++draft/basic.fundamental#12.sentence-1). (Though they might have the same range of values and internal bit representation.) – Daniel Langr Nov 25 '19 at 13:13
  • @DanielsaysreinstateMonica: So the language standard guarantees that they will be regarded as different types even if they're identical in every possible aspect, thus ensuring that this code will compile on every platform...? – goodvibration Nov 25 '19 at 13:17
  • @goodvibration Exactly. If they would be treated the same (as type aliases), we practically wouldn't be able to use partial specialization or function overloading in such cases (floating-point types, integral types, character types, pointer types, etc). – Daniel Langr Nov 25 '19 at 13:19
  • 2
    @Caduchon, why do you think you can't use a function call as default argument? AFAIK, that has never been true. – AProgrammer Nov 25 '19 at 13:28
  • @AProgrammer, well, it seems that it solve my problem. thanks. :-) – Caduchon Nov 25 '19 at 13:41

2 Answers2

2

If you don't want to use std::numeric_limits<FloatType>::epsilon(), I think you can do as proposed in comments: Initialize the static members outside class definition.

You can write:

#include <cmath>

template <typename FloatType>
struct default_epsilon
{};

template <>
struct default_epsilon<float>
{
    static const float value;
};
template <>
struct default_epsilon<double>
{
    static const double value;
};
template <>
struct default_epsilon<long double>
{
    static const long double value;
};

template <typename FloatType>
bool approx(FloatType x1, FloatType x2)
{
    return std::abs(x2 - x1) < default_epsilon<FloatType>::value;
}

And somewhere in your .cpp file:

const float default_epsilon<float>::value = 1.0e-05f;
const double default_epsilon<double>::value = 1.0e-10;
const long double default_epsilon<long double>::value = 1.0e-12l;

And it should do the trick.

Fareanor
  • 5,900
  • 2
  • 11
  • 37
0

use traditional c++ casting operator overload

class MyEpsilon
{
public:
    operator float() {return 1.0e-05f;};
    operator double() { return 1.0e-10; };
    operator long double() { return 1.0e-12l; };
};

template<class T>
class MyTest {
public:
    T DoSome() {
        MyEpsilon e;
        return T(e);
    }
};
Ahmed Anter
  • 650
  • 4
  • 13