假设我有一个包含值的LimitedValue类,并在int类型'min'和'max'上进行参数化.您可以将它用作容纳值的容器,该值只能在一定范围内.你可以使用它:
LimitedValue< float, 0, 360 > someAngle( 45.0 ); someTrigFunction( someAngle );
这样'someTrigFunction'知道它可以保证提供有效的输入(如果参数无效,构造函数会抛出异常).
但是,复制构造和分配仅限于完全相同的类型.我希望能够做到:
LimitedValue< float, 0, 90 > smallAngle( 45.0 ); LimitedValue< float, 0, 360 > anyAngle( smallAngle );
并在编译时检查该操作,因此下一个示例给出了一个错误:
LimitedValue< float, -90, 0 > negativeAngle( -45.0 ); LimitedValue< float, 0, 360 > postiveAngle( negativeAngle ); // ERROR!
这可能吗?是否有一些实际的方法可以做到这一点,或者有哪些例子可以解决这个问题?
您可以使用模板执行此操作 - 尝试以下操作:
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 };
好的,这是没有Boost依赖的C++ 11.
在编译时检查类型系统保证的所有内容,并且其他任何内容都会抛出异常.
我添加unsafe_bounded_cast
了可能抛出的转换,以及safe_bounded_cast
静态正确的显式转换(这是多余的,因为复制构造函数处理它,但提供了对称性和表达性).
#include "bounded.hpp" int main() { BoundedValueinner(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 }
这有点样板,但是如上所述给出了相当可读的异常消息(如果你选择捕获派生的异常类型并且可以对它做一些有用的事情,那么实际的最小/最大/值也会被公开).
#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()); }
templateclass 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_; };
templatestruct 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); }
Boost Constrained Value库(1)允许您向数据类型添加约束.
但是当你想将它与float类型一起使用时,你必须阅读" 为什么C++的浮点类型不应该与有界对象一起使用? "(如你的例子中所示).
(1) Boost Constrained Value库还不是官方的Boost库.