25

値を保持し、int型「min」および「max」でパラメーター化されたLimitedValueクラスがあるとします。特定の範囲にのみ存在できる値を保持するためのコンテナとして使用します。あなたはそれをそのように使うことができます:

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!

これは可能ですか?これを行うための実用的な方法、またはこれにアプローチする例はありますか?

4

9 に答える 9

19

OK、これは 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);
}
于 2012-12-05T18:49:50.440 に答える
18

テンプレートを使用してこれを行うことができます -- 次のようにしてみてください:

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
};
于 2008-09-29T13:00:35.493 に答える
5

Boost Constrained Value ライブラリ(1)を使用すると、データ型に制約を追加できます。

ただし、フロート型で使用したい場合は、「 Why C++'s floating point types should not be used with bounded objects? 」というアドバイスを読む必要があります (例に示されているように)。

(1) Boost Constrained Value ライブラリは、まだ公式の Boost ライブラリではありません。

于 2008-09-29T16:36:03.243 に答える
3

これは実際には複雑な問題であり、私はしばらくの間それに取り組んできました...

これで、コード内の浮動小数点と整数を制限できる公的に利用可能なライブラリができたので、それらが常に有効であることをより確実にすることができます。

最終リリースバージョンで制限をオフにできるだけでなく、タイプがとほぼ同じになることを意味しますtypedef

タイプを次のように定義します。

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

CONTROLLED_VARS_DEBUGまた、フラグとCONTROLLED_VARS_LIMITEDフラグを定義しない場合は、次のようになります。

typedef float angle_t;

これらのクラスは生成されるため、使用時にあまり苦労しないようにするために必要なすべての演算子が含まれています。つまり、angle_tほぼをとして見ることができますfloat

angle_t a;
a += 35;

期待どおりに機能します(場合はスローしますa + 35 > 360)。

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

これが2008年に投稿されたことは知っていますが、この機能を提供するトップライブラリへの適切なリンクが見つかりません!?


float a; double b; a = b;このライブラリを使用したい人のための補足として、ライブラリが値(つまり、int c; long d; c = d;)のサイズをサイレントに変更し、コードにあらゆる種類の問題を引き起こす可能性があることに気づきました。ライブラリの使用には注意してください。

于 2011-10-28T11:37:01.013 に答える
2

Ada の の機能を模倣する C++ クラスを作成しましたrange

ここで提供されるソリューションと同様に、テンプレートに基づいています。

このようなものが実際のプロジェクトで使用される場合、非常に基本的な方法で使用されます。微妙なバグや誤解が悲惨な結果になる可能性があります。

したがって、多くのコードのない小さなライブラリですが、私の意見では、単体テストの提供と明確な設計哲学は非常に重要です。

気軽に試してみて、問題があれば教えてください。

https://github.com/alkhimey/ConstrainedTypes

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

于 2014-09-10T17:28:29.977 に答える
1

現時点では、定数引数を使用してもメソッド(ひいてはコンストラクター)を呼び出す方法に関するC ++の規則により、移植可能な方法では不可能です。

C ++ 0x標準では、このようなエラーを生成できるようにするconst-exprを使用できます。

(これは、実際の値が不正な場合にのみエラーをスローすることを前提としています。範囲が一致しない場合は、これを実現できます)

于 2008-09-29T12:53:58.330 に答える
1

テンプレートについて覚えておくべきことの 1 つは、テンプレート パラメーターの一意のセットを呼び出すたびに、比較と割り当てによってコンパイル エラーが生成される「一意の」クラスが生成されることです。これを回避する方法を知っているメタプログラミングの達人がいるかもしれませんが、私はその一人ではありません。私のアプローチは、実行時チェックとオーバーロードされた比較演算子と代入演算子を使用してクラスにこれらを実装することです。

于 2008-09-29T13:16:18.730 に答える
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++ テンプレート メカニズムでは表現できません。

すべての Bounded 仕様は新しいタイプになります。したがって、コンパイラは型の不一致をチェックできます。これらのタイプに存在する可能性がある、より高度な関係をチェックすることはできません。

于 2008-09-29T13:44:06.917 に答える