Limiting range of value types in C++

前端 未结 9 779
伪装坚强ぢ
伪装坚强ぢ 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 inner(1);
        BoundedValue outer(2.3);
        BoundedValue 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(inner);
        // ^ error: static assertion failed: "conversion disallowed from BoundedValue with higher max"
    
        overlap = unsafe_bounded_cast(inner);
        // ^ compiles but throws:
        // terminate called after throwing an instance of 'BoundedValueException'
        //   what():  BoundedValueException: !(-1<=2<=1) - BOUNDED_VALUE_ASSERT at bounded.hpp:56
        // Aborted
    
        inner = 0;
        overlap = unsafe_bounded_cast(inner);
        // ^ ok
    
        inner = 7;
        // terminate called after throwing an instance of 'BoundedValueException'
        //   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 
    #include 
    
    #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 
    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  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(min, max, val, oss.str());
    }
    

    Value Class

    template  class BoundedValue
    {
    public:
        typedef T value_type;
        enum { min_value=Tmin, max_value=Tmax };
        typedef BoundedValue 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 
        BoundedValue(BoundedValue 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 
        BoundedValue& operator= (BoundedValue 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 
    struct BoundedCastHelper
    {
        typedef BoundedValue return_type;
    
        // conversion is checked statically, and always succeeds
        template 
        static return_type convert(BoundedValue const& source)
        {
            return return_type(source);
        }
    
        // conversion is checked dynamically, and could throw
        template 
        static return_type coerce(BoundedValue const& source)
        {
            return return_type(static_cast(source));
        }
    };
    
    template 
    auto safe_bounded_cast(BoundedValue const& source)
        -> BoundedValue
    {
        return BoundedCastHelper::convert(source);
    }
    
    template 
    auto unsafe_bounded_cast(BoundedValue const& source)
        -> BoundedValue
    {
        return BoundedCastHelper::coerce(source);
    }
    

提交回复
热议问题