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?
Upper limit of data type Number (Integer) is 2,147,483,647.
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.
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).
#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 }
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()); }
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_; };
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); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With