限制C ++中的值类型范围

时间:2008-09-29 12:43:53

标签: c++ templates

假设我有一个包含值的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!

这可能吗?有没有一些实用的方法可以做到这一点,或者有哪些例子可以解决这个问题?

9 个答案:

答案 0 :(得分:17)

您可以使用模板执行此操作 - 尝试以下操作:

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
};

答案 1 :(得分:13)

好的,这是没有Boost依赖的C ++ 11。

在编译时检查类型系统保证的所有内容,其他任何内容都会抛出异常。

我为可能投掷的转化添加了unsafe_bounded_cast,并为静态正确的显式转换添加了safe_bounded_cast(这是多余的,因为复制构造函数处理它,但是提供对称性和表现力。)

使用示例

#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
}

异常支持

这是一个样板文件,但是如上所述给出了相当可读的异常消息(如果你选择捕获派生的异常类型并且可以对它做一些有用的事情,那么实际的最小/最大/值也会被公开)。

#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);
}

答案 2 :(得分:5)

The Boost Constrained Value library (1)允许您向数据类型添加约束。

但是当你想将它与float类型一起使用时,你必须阅读建议“Why C++'s floating point types shouldn't be used with bounded objects?”(如你的例子所示)。

(1) Boost Constrained Value库尚未成为官方的Boost库。

答案 3 :(得分:3)

有界::整数库可以满足您的需求(仅适用于整数类型)。 http://doublewise.net/c++/bounded/

(为了完全披露,我是这个图书馆的作者)

它与其他试图提供&#34;安全整数的库不同。以一种重要的方式:它跟踪整数边界。我认为这是最好的例子:

auto x = bounded::checked_integer<0, 7>(f());
auto y = 7_bi;
auto z = x + y;
// decltype(z) == bounded::checked_integer<7, 14>
static_assert(z >= 7_bi);
static_assert(z <= 14_bi);

x是介于0和7之间的整数类型.y是介于7和7之间的整数类型.z是介于7和14之间的整数类型。所有这些信息在编译时都是已知的,这就是为什么我们是能够对其进行static_assert,即使z的值不是编译时常量。

z = 10_bi;
z = x;
static_assert(!std::is_assignable<decltype((z)), decltype(0_bi)>::value);

第一项作业z = 10_bi未经检查。这是因为编译器可以证明10落在z范围内。

第二项作业z = x会检查x的值是否在z的范围内。如果没有,它会抛出异常(确切的行为取决于您使用的整数类型,有许多策略可以做什么)。

第三行static_assert表明从一个完全没有重叠的类型中分配是一个编译时错误。编译器已经知道这是一个错误并阻止你。

库不会隐式转换为基础类型,因为这会导致许多情况,即您尝试阻止某些内容但由于转换而发生。它确实允许显式转换。

答案 4 :(得分:2)

这实际上是一个复杂的问题,我已经解决了一段时间......

现在我有一个公开的库,允许您限制代码中的浮点和整数,以便您可以更加确定它们始终有效。

不仅可以在最终版本中关闭限制,这意味着类型与typedef几乎相同。

将您的类型定义为:

typedef controlled_vars::limited_fauto_init<float, 0, 360> angle_t;

当你没有定义CONTROLLED_VARS_DEBUGCONTROLLED_VARS_LIMITED标志时,你会得到与此基本相同的东西:

typedef float angle_t;

生成这些类,因此它们包含所有必要的运算符,以便您在使用它们时不会受到太多影响。这意味着您可以将angle_t视为float

angle_t a;
a += 35;

将按预期工作(并在a + 35 > 360时抛出)。

http://snapwebsites.org/project/controlled-vars

我知道这是在2008年发布的......但我没有看到任何提供此功能的顶级图书馆的良好链接!?

答案 5 :(得分:1)

目前,由于关于如何使用常量参数调用方法(以及扩展,构造函数)的C ++规则,以可移植的方式这是不可能的。

在C ++ 0x标准中,您可以使用const-expr来生成这样的错误。

(假设您希望它仅在实际值非法时才会抛出错误。如果范围不匹配,您可以实现此目的)

答案 6 :(得分:1)

关于模板的一件事要记住,每次调用一组唯一的模板参数都会产生一个“唯一”类,比较和赋值会产生编译错误。可能有一些元编程大师可能知道如何解决这个问题,但我不是其中之一。我的方法是在具有运行时检查和重载比较和赋值运算符的类中实现它们。

答案 7 :(得分:1)

我想为Kasprzol的解决方案提供备用版本:建议的方法总是使用int类型的边界。您可以通过以下实现获得更多灵活性和类型安全性:

template<typename T, T min, T max>
class Bounded {
private:
    T _value;
public:
    Bounded(T value) : _value(min) {
        if (value <= max && value >= min) {
            _value = value;
       } else {
           // XXX throw your runtime error/exception...
       }
    }
    Bounded(const Bounded<T, min, max>& b)
        : _value(b._value){ }
};

这将允许类型检查器捕获明显的未命中分配,例如:

Bounded<int, 1, 5> b1(1);
Bounded<int, 1, 4> b2(b1); // <-- won't compile: type mismatch

但是,要检查一个模板实例的范围是否包含在另一个实例范围内的更高级关系无法在C ++模板机制中表示。

每个有界规范都成为新类型。因此编译器可以检查类型不匹配。它无法检查这些类型可能存在的更高级关系。

答案 8 :(得分:1)

我写了一个模仿Ada range功能的C ++课。

它基于模板,类似于此处提供的解决方案。

如果要在实际项目中使用这样的东西,它将以非常基本的方式使用。微妙的错误或误解可能是灾难性的。

因此,虽然它是一个没有大量代码的小型库,但在我看来,提供单元测试和清晰的设计理念非常重要。

随意尝试,如果您发现任何问题请告诉我。

https://github.com/alkhimey/ConstrainedTypes

http://www.nihamkin.com/2014/09/05/range-constrained-types-in-c++/