tags:

views:

698

answers:

5

Suppose I have a LimitedValue class which holds a value, and is parameterized on int types 'min' and 'max'. You'd use it as a container for holding values which can only be in a certain range. You could use it such:

LimitedValue< float, 0, 360 > someAngle( 45.0 );
someTrigFunction( someAngle );

so that 'someTrigFunction' knows that it is guaranteed to be supplied a valid input (The constructor would throw an exception if the parameter is invalid).

Copy-construction and assignment are limited to exactly equal types, though. I'd like to be able to do:

LimitedValue< float, 0, 90 > smallAngle( 45.0 );
LimitedValue< float, 0, 360 > anyAngle( smallAngle );

and have that operation checked at compile-time, so this next example gives an error:

LimitedValue< float, -90, 0 > negativeAngle( -45.0 );
LimitedValue< float, 0, 360 > postiveAngle( negativeAngle ); // ERROR!

Is this possible? Is there some practical way of doing this, or any examples out there which approach this?

+1  A: 

At the moment, that is impossible in a portable manner due to the C++ rules on how methods (and by extension, constructors) are called even with constant arguments.

In the C++0x standard, you could have a const-expr that would allow such an error to be produced though.

(This is assuming you want it to throw an error only if the actual value is illegal. If the ranges do not match, you can achieve this)

workmad3
+10  A: 

You can do this using templates -- try something like this:

#include <boost/static_assert.hpp>
template< typename T, int min, int max >class LimitedValue {
   template< int min2, int max2 >LimitedValue( const LimitedValue< T, min2, max2 > &other )
   {
   BOOST_STATIC_ASSERT( min <= min2 );
   BOOST_STATIC_ASSERT( max >= max2 );

   // logic
   }
// rest of code
};
Kasprzol
A: 

One thing to remember about templates is that each invocation of a unique set of template parameters will wind up generating a "unique" class for which comparisons and assignments will generate a compile error. There may be some meta-programming gurus that might know how to work around this but I am not one of them. My approach would be to implement these in a class with run-time checks and overloaded comparison and assignment operators.

Jon Trauntvein
+1  A: 

I'd like to offer an alternate version for Kasprzol's solution: The proposed approach always uses bounds of type int. You can get some more flexibility and type safety with an implementation such as this:

template<typename T, T min, T max>
class Bounded {
private:
    T _value;
public:
    Bounded(T value) : _value(min) {
        if (value <= max && value >= min) {
            _value = value;
       } else {
           // XXX throw your runtime error/exception...
       }
    }
    Bounded(const Bounded<T, min, max>& b)
        : _value(b._value){ }
};

This will allow the type checker to catch obvious miss assignments such as:

Bounded<int, 1, 5> b1(1);
Bounded<int, 1, 4> b2(b1); // <-- won't compile: type mismatch

However, the more advanced relationships where you want to check whether the range of one template instance is included within the range of another instance cannot be expressed in the C++ template mechanism.

Every Bounded specification becomes a new type. Thus the compiler can check for type mismatches. It cannot check for more advanced relationships that might exist for those types.

VoidPointer
This proposed solution would only work for int and integral type ranges as a template parameterised on the value of any other type won't work (you can't parameterise a template on a float for example).
workmad3
+1  A: 

The Boost Constrained Value library(1) allows you to add constrains to data types.

But you have to read the advice "Why C++'s floating point types shouldn't be used with bounded objects?" when you like to use it with float types (as illustrated in your example).

(1) The Boost Constrained Value library is not an official Boost library yet.

jk