Creating a number with a range

J

Joe Van Dyk

Hi,

Say I have:
class Latitude
{
friend bool operator==(const Latitude& lhs, const Latitude& rhs);
friend bool operator<(const Latitude& lhs, const Latitude& rhs);
friend std::eek:stream& operator<<(std::eek:stream& os, const Latitude& l);

public:
explicit Latitude(double latitude);
double latitude() const { return latitude_; }

private:
double latitude_;
};


So, it's essentially a wrapper around a double. The constructor enforces
that the latitude can't be less than -90 or greater than 90. Comparison
operators do the comparison of the double.

Now, I've got to do the same with Longitude and Altitude. They're all
kinda the same, except that Longitude can range from -180 to 180, and
Altitude doesn't really have a limit (I guess its the limit on what a
double can hold).

How can I consolidate this logic? Templates? Inheritance? Or am I going
about this completely wrong?

Joe
 
B

blytkerchan

Joe said:
Hi,

Say I have:
class Latitude
{
friend bool operator==(const Latitude& lhs, const Latitude& rhs);
friend bool operator<(const Latitude& lhs, const Latitude& rhs);
friend std::eek:stream& operator<<(std::eek:stream& os, const Latitude& l);

public:
explicit Latitude(double latitude);
double latitude() const { return latitude_; }

private:
double latitude_;
};


So, it's essentially a wrapper around a double. The constructor enforces
that the latitude can't be less than -90 or greater than 90. Comparison
operators do the comparison of the double.

Now, I've got to do the same with Longitude and Altitude. They're all
kinda the same, except that Longitude can range from -180 to 180, and
Altitude doesn't really have a limit (I guess its the limit on what a
double can hold).

How can I consolidate this logic? Templates? Inheritance? Or am I going
about this completely wrong?
One more or less elegant way to approach this might be to use a policy
for validation:

-- BEGIN EXAMPLE CODE --
// DerangedDouble.h
#include <stdexcept>

template < class ValidationPolicy >
class DerangedDouble
{
public :
DerangedDouble( double d )
: d_(d)
{
if (!ValidationPolicy::validate(d))
throw std::range_error("value out of range!");
else
{ /* all is well */ }
}

private :
double d_;
};

// ValidationPolicies.h
struct LatitudeValidation
{
static bool validate(double d)
{
return d < 90 && d > -90;
}
};

struct LongitudeValidation
{
static bool validate(double d)
{
return d >= -180 && d <= 180;
}
};

struct AltitudeValidation
{
static bool validate(double d)
{
return d >= 0;
}
};

// test.cpp
#include "DerangedDouble.h"
#include "ValidationPolicies.h"

typedef DerangedDouble< LongitudeValidation > Longitude;
typedef DerangedDouble< LatitudeValidation > Latitude;
typedef DerangedDouble< AltitudeValidation > Altitude;

int main()
{
Longitude longitude(0);
Latitude latitude(0);
Altitude altitude(0);

return 0;
}
--- END EXAMPLE CODE ---
The fun with that is that you can easily make another class, say
"Temperature" with a TemperatureValidation policy.
Have a look at Andrei Alexandrescu's book "Modern C++ Design" for more
extensive information on policies :)

rlc
 
J

Joe Van Dyk

One more or less elegant way to approach this might be to use a policy
for validation:

-- BEGIN EXAMPLE CODE --
// DerangedDouble.h
#include <stdexcept>

template < class ValidationPolicy >
class DerangedDouble
{
public :
DerangedDouble( double d )
: d_(d)
{
if (!ValidationPolicy::validate(d))
throw std::range_error("value out of range!");
else
{ /* all is well */ }
}

private :
double d_;
};

// ValidationPolicies.h
struct LatitudeValidation
{
static bool validate(double d)
{
return d < 90 && d > -90;
}
};

struct LongitudeValidation
{
static bool validate(double d)
{
return d >= -180 && d <= 180;
}
};

struct AltitudeValidation
{
static bool validate(double d)
{
return d >= 0;
}
};

// test.cpp
#include "DerangedDouble.h"
#include "ValidationPolicies.h"

typedef DerangedDouble< LongitudeValidation > Longitude;
typedef DerangedDouble< LatitudeValidation > Latitude;
typedef DerangedDouble< AltitudeValidation > Altitude;

int main()
{
Longitude longitude(0);
Latitude latitude(0);
Altitude altitude(0);

return 0;
}
--- END EXAMPLE CODE ---
The fun with that is that you can easily make another class, say
"Temperature" with a TemperatureValidation policy.
Have a look at Andrei Alexandrescu's book "Modern C++ Design" for more
extensive information on policies :)

rlc

Thanks! I was thinking something along those lines. Why is
AltitudeValidation::validate static?

Joe
 
J

Joe Van Dyk

Thanks! I was thinking something along those lines. Why is
AltitudeValidation::validate static?

Joe

Oh, nevermind, I get it. That make it a class function, or whatever the
correct terminology is.

template<class ValidationPolicy>
class BaseNumber
{
public:
explicit BaseNumber(double val)
: value_(0)
{
if (!ValidationPolicy::validate(val))
{
throw std::range_error(std::string("Needs to be between "
+ ValidationPolicy::min + " and "
+ ValidationPolicy::max));
}
value_ = val;
}

double value() const { return value_; }

private:
double value_;
};

struct BaseValidation
{
static double max, min;
static bool validate(double val)
{
return val >= min && val <= max;
}
};

struct AltitudeValidation : public BaseValidation
{
AltitudeValidation() { max = 10000000; min = 0; }
};
typedef BaseNumber<AltitudeValidation> Altitude;


It's not working, since I can't access max and min inside
BaseValidation::validate.

Is something like the above possible? Or am I completely going in the
wrong direction?

Joe
 
J

Jerry Coffin

[ ... ]
So, it's essentially a wrapper around a double. The constructor enforces
that the latitude can't be less than -90 or greater than 90. Comparison
operators do the comparison of the double.

You've gotten (at least) one suggestion already, but IMO, using a policy
to do nothing more than specify a range is overkill. I've posted this a
few times before, but I guess once more won't hurt too much:

#include <exception>
#include <iostream>
#include <functional>

template <class T, T lower, T upper, class less=std::less<T> >
class bounded {
T val;

static bool check(T const &value) {
return less()(value, lower) || less()(upper, value);
}

public:
bounded() : val(lower) { }

bounded(T const &v) {
if (check(v))
throw std::domain_error("out of range");
val = v;
}

bounded(bounded const &init) : val(init.v) {}

bounded &operator=(T const &v) {
if (check(v))
throw std::domain_error("Out of Range");
val = v;
return *this;
}

operator T() const { return val; }

friend std::istream &operator>>(std::istream &is, bounded &b) {
T temp;
is >> temp;

if (check(temp))
is.setstate(std::ios::failbit);
b.val = temp;
return is;
}
};

It might make sense to use a policy template, but the policy would be to
govern what happens when you attempt to assign an out-of-range value. As
it stands right now, this throws an exception, but for different
circumstances, it might be reasoanble to reduce the new value modulo the
range (or upper value of the range), or simply fail the assignment,
leaving the variable unaltered. I've never had a need for that, however,
so I've never gotten beyond the point of mentioning it in a post or two.
 
M

Markus Schoder

Jerry said:
[ ... ]
So, it's essentially a wrapper around a double. The constructor enforces
that the latitude can't be less than -90 or greater than 90. Comparison
operators do the comparison of the double.

You've gotten (at least) one suggestion already, but IMO, using a policy
to do nothing more than specify a range is overkill. I've posted this a
few times before, but I guess once more won't hurt too much:

#include <exception>
#include <iostream>
#include <functional>

template <class T, T lower, T upper, class less=std::less<T> >
class bounded {

But T would need to be an integral type here making it useless
for the OP.
 
J

Joe Van Dyk

Jerry said:
[ ... ]
So, it's essentially a wrapper around a double. The constructor enforces
that the latitude can't be less than -90 or greater than 90. Comparison
operators do the comparison of the double.

You've gotten (at least) one suggestion already, but IMO, using a policy
to do nothing more than specify a range is overkill. I've posted this a
few times before, but I guess once more won't hurt too much:

#include <exception>
#include <iostream>
#include <functional>

template <class T, T lower, T upper, class less=std::less<T> >
class bounded {

But T would need to be an integral type here making it useless
for the OP.

As T can't be a floating point type, right? Is that going to be fixed in
an upcoming standard?
 
J

Jerry Coffin

[ ... ]
But T would need to be an integral type here making it useless
for the OP.

Sorry -- I'd meant to mention that (at least IMO) most things that deal
with hard boundaries probably shouldn't be using floating point in the
first place. Floating point and "hard" are almost mutually exclusive
concepts. A floating point number usually needs to be viewed as a rather
soft, imprecise kind of thing, so if you're dealing with hard limits,
chances are you shouldn't be using a floating point number.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top