Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Limiting range of value types in C++

Tags:

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?

like image 253
user23434 Avatar asked Sep 29 '08 12:09

user23434


People also ask

Is there any limit to data type?

Upper limit of data type Number (Integer) is 2,147,483,647.

What is the range of float in C?

Since the high-order bit of the mantissa is always 1, it is not stored in the number. This representation gives a range of approximately 3.4E-38 to 3.4E+38 for type float. You can declare variables as float or double, depending on the needs of your application.


1 Answers

OK, this is C++11 with no Boost dependencies.

Everything guaranteed by the type system is checked at compile time, and anything else throws an exception.

I've added unsafe_bounded_cast for conversions that may throw, and safe_bounded_cast for explicit conversions that are statically correct (this is redundant since the copy constructor handles it, but provided for symmetry and expressiveness).

Example Use

#include "bounded.hpp"  int main() {     BoundedValue<int, 0, 5> inner(1);     BoundedValue<double, 0, 4> outer(2.3);     BoundedValue<double, -1, +1> overlap(0.0);      inner = outer; // ok: [0,4] contained in [0,5]      // overlap = inner;     // ^ error: static assertion failed: "conversion disallowed from BoundedValue with higher max"      // overlap = safe_bounded_cast<double, -1, +1>(inner);     // ^ error: static assertion failed: "conversion disallowed from BoundedValue with higher max"      overlap = unsafe_bounded_cast<double, -1, +1>(inner);     // ^ compiles but throws:     // terminate called after throwing an instance of 'BoundedValueException<int>'     //   what():  BoundedValueException: !(-1<=2<=1) - BOUNDED_VALUE_ASSERT at bounded.hpp:56     // Aborted      inner = 0;     overlap = unsafe_bounded_cast<double, -1, +1>(inner);     // ^ ok      inner = 7;     // terminate called after throwing an instance of 'BoundedValueException<int>'     //   what():  BoundedValueException: !(0<=7<=5) - BOUNDED_VALUE_ASSERT at bounded.hpp:75     // Aborted } 

Exception Support

This is a bit boilerplate-y, but gives fairly readable exception messages as above (the actual min/max/value are exposed as well, if you choose to catch the derived exception type and can do something useful with it).

#include <stdexcept> #include <sstream>  #define STRINGIZE(x) #x #define STRINGIFY(x) STRINGIZE( x )  // handling for runtime value errors #define BOUNDED_VALUE_ASSERT(MIN, MAX, VAL) \     if ((VAL) < (MIN) || (VAL) > (MAX)) { \         bounded_value_assert_helper(MIN, MAX, VAL, \                                     "BOUNDED_VALUE_ASSERT at " \                                     __FILE__ ":" STRINGIFY(__LINE__)); \     }  template <typename T> struct BoundedValueException: public std::range_error {     virtual ~BoundedValueException() throw() {}     BoundedValueException() = delete;     BoundedValueException(BoundedValueException const &other) = default;     BoundedValueException(BoundedValueException &&source) = default;      BoundedValueException(int min, int max, T val, std::string const& message)         : std::range_error(message), minval_(min), maxval_(max), val_(val)     {     }      int const minval_;     int const maxval_;     T const val_; };  template <typename T> void bounded_value_assert_helper(int min, int max, T val,                                                        char const *message = NULL) {     std::ostringstream oss;     oss << "BoundedValueException: !("         << min << "<="         << val << "<="         << max << ")";     if (message) {         oss << " - " << message;     }     throw BoundedValueException<T>(min, max, val, oss.str()); } 

Value Class

template <typename T, int Tmin, int Tmax> class BoundedValue { public:     typedef T value_type;     enum { min_value=Tmin, max_value=Tmax };     typedef BoundedValue<value_type, min_value, max_value> SelfType;      // runtime checking constructor:     explicit BoundedValue(T runtime_value) : val_(runtime_value) {         BOUNDED_VALUE_ASSERT(min_value, max_value, runtime_value);     }     // compile-time checked constructors:     BoundedValue(SelfType const& other) : val_(other) {}     BoundedValue(SelfType &&other) : val_(other) {}      template <typename otherT, int otherTmin, int otherTmax>     BoundedValue(BoundedValue<otherT, otherTmin, otherTmax> const &other)         : val_(other) // will just fail if T, otherT not convertible     {         static_assert(otherTmin >= Tmin,                       "conversion disallowed from BoundedValue with lower min");         static_assert(otherTmax <= Tmax,                       "conversion disallowed from BoundedValue with higher max");     }      // compile-time checked assignments:     BoundedValue& operator= (SelfType const& other) { val_ = other.val_; return *this; }      template <typename otherT, int otherTmin, int otherTmax>     BoundedValue& operator= (BoundedValue<otherT, otherTmin, otherTmax> const &other) {         static_assert(otherTmin >= Tmin,                       "conversion disallowed from BoundedValue with lower min");         static_assert(otherTmax <= Tmax,                       "conversion disallowed from BoundedValue with higher max");         val_ = other; // will just fail if T, otherT not convertible         return *this;     }     // run-time checked assignment:     BoundedValue& operator= (T const& val) {         BOUNDED_VALUE_ASSERT(min_value, max_value, val);         val_ = val;         return *this;     }      operator T const& () const { return val_; } private:     value_type val_; }; 

Cast Support

template <typename dstT, int dstMin, int dstMax> struct BoundedCastHelper {     typedef BoundedValue<dstT, dstMin, dstMax> return_type;      // conversion is checked statically, and always succeeds     template <typename srcT, int srcMin, int srcMax>     static return_type convert(BoundedValue<srcT, srcMin, srcMax> const& source)     {         return return_type(source);     }      // conversion is checked dynamically, and could throw     template <typename srcT, int srcMin, int srcMax>     static return_type coerce(BoundedValue<srcT, srcMin, srcMax> const& source)     {         return return_type(static_cast<srcT>(source));     } };  template <typename dstT, int dstMin, int dstMax,           typename srcT, int srcMin, int srcMax> auto safe_bounded_cast(BoundedValue<srcT, srcMin, srcMax> const& source)     -> BoundedValue<dstT, dstMin, dstMax> {     return BoundedCastHelper<dstT, dstMin, dstMax>::convert(source); }  template <typename dstT, int dstMin, int dstMax,           typename srcT, int srcMin, int srcMax> auto unsafe_bounded_cast(BoundedValue<srcT, srcMin, srcMax> const& source)     -> BoundedValue<dstT, dstMin, dstMax> {     return BoundedCastHelper<dstT, dstMin, dstMax>::coerce(source); } 
like image 63
Useless Avatar answered Oct 06 '22 00:10

Useless