Limiting range of value types in C++

前端 未结 9 694
伪装坚强ぢ
伪装坚强ぢ 2020-11-28 10:33

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 on

相关标签:
9条回答
  • 2020-11-28 11:20

    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);
    }
    
    0 讨论(0)
  • 2020-11-28 11:21

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

    template< typename T, int min, int max >class LimitedValue {
       template< int min2, int max2 >LimitedValue( const LimitedValue< T, min2, max2 > &other )
       {
       static_assert( min <= min2, "Parameter minimum must be >= this minimum" );
       static_assert( max >= max2, "Parameter maximum must be <= this maximum" );
    
       // logic
       }
    // rest of code
    };
    
    0 讨论(0)
  • 2020-11-28 11:22

    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)

    0 讨论(0)
提交回复
热议问题