Supongamos que tengo un LimitedValue clase que tiene un valor, y es parametrizado en int tipos de » min » y «max». Te gustaría utilizar como un contenedor para la celebración de valores que sólo pueden estar en un cierto rango. Usted puede usarlo como:

LimitedValue< float, 0, 360 > someAngle( 45.0 );
someTrigFunction( someAngle );

por lo que ‘someTrigFunction’ sabe que está garantizado para ser suministrado una entrada válida (El constructor podría lanzar una excepción si el parámetro no es válido).

Copia de la construcción y de la asignación se limitan a ser exactamente iguales tipos, aunque. Me gustaría ser capaz de hacer:

LimitedValue< float, 0, 90 > smallAngle( 45.0 );
LimitedValue< float, 0, 360 > anyAngle( smallAngle );

y que la operación se comprueban en tiempo de compilación, por lo que este siguiente ejemplo da un error:

LimitedValue< float, -90, 0 > negativeAngle( -45.0 );
LimitedValue< float, 0, 360 > postiveAngle( negativeAngle ); //ERROR!

Es esto posible? ¿Hay alguna forma práctica de hacer esto, o cualquiera de los ejemplos que hay que acercarse a este?

InformationsquelleAutor user23434 | 2008-09-29

9 Comentarios

  1. 15

    Usted puede hacer esto mediante el uso de plantillas — intentar algo como esto:

    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
    };
    • Tenga en cuenta que esto es incorrecto. El último ejemplo muestra un límite que no encaja (es decir, -90 a 0 frente a 0 a 360,) sin embargo, un ángulo de 0 a satisfacer ambos tipos de variables. Por lo tanto, si el ángulo es 0 en lugar de -45 no obtendrá el error marcado en el último ejemplo.
    • Esta respuesta es totalmente equivocado. OP especificado un tiempo de ejecución de comprobar que el parámetro de ajuste en el lado izquierdo tipo. static_assert es para compilar el tiempo de comprobación, que también puede ser útil, pero no responde a la pregunta. Además, el genericity se ha ido desde el min y max debe ser especificado como int.
  2. 14

    OK, este es el C++11 sin Aumentar las dependencias.

    Todo garantizado por el tipo de sistema se comprueban en tiempo de compilación, y cualquier otra cosa que lanza una excepción.

    He añadido unsafe_bounded_cast para las conversiones que puede tirar, y safe_bounded_cast de las conversiones explícitas que son estáticamente correcto (esto es redundante, ya que el constructor de copia maneja, pero siempre por la simetría y la expresividad).

    Ejemplo De Uso

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

    Excepción Apoyo

    Esto es un poco repetitivo-y, pero da bastante legible mensajes de excepción como la de arriba (el real de la min/max/valor están expuestos, así, si usted elige para la captura de los derivados de tipo de excepción y puede hacer algo útil con ella).

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

    Clase De Valor

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

    Elenco De Apoyo

    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);
    }
    • En la primera(explícito) constructor no debe ser T runtime_value en lugar de int runtime_value?
    • Supone un tipo interno…OP quiere número real de apoyo.
    • ¿Te refieres a una integral tipo? El tipo de almacenamiento es una plantilla param y funciona bien con el doble. El límites son enteros, como se muestra en la pregunta.
    • constexpr sería una mejor opción que enum ya que se supone que esta es una C++11 solución.
  3. 5

    El Impulso Limitado Valor de la biblioteca(1) le permite agregar limita a los tipos de datos.

    Pero usted tiene que leer los consejos «Por qué C++tipos de punto flotante no debe ser utilizado con delimitada objetos?» cuando usted quiera usar con flotador tipo (como se ilustra en el ejemplo).

    (1) El Impulso Limitado Valor de la biblioteca no es un funcionario de Impulsar la biblioteca, sin.

  4. 3

    El delimitada::integer biblioteca hace lo que quiere (para los tipos enteros solamente). http://doublewise.net/c++/limitada/

    (En aras de la divulgación completa, yo soy el autor de esta biblioteca)

    Se diferencia de otras bibliotecas que intento de proporcionar un «seguro enteros» de una manera significativa: se realiza un seguimiento entero límites. Creo que esta es la mejor muestra por ejemplo:

    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 es un número entero entre 0 y 7. y es de tipo entero entre 7 y 7. z es un número entero entre 7 y 14. Toda esta información se conoce en tiempo de compilación, por lo que son capaces de static_assert en él, incluso a pesar de que el valor de z no es una constante en tiempo de compilación.

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

    La primera asignación, z = 10_bi, está desactivada. Esto es porque el compilador puede demostrar que 10 cae dentro del rango de z.

    De la segunda asignación, z = x, comprueba que el valor de x está dentro de la gama de z. Si no, se produce una excepción (el comportamiento exacto depende del tipo de número entero que utilice, hay muchas pólizas de qué hacer).

    La tercera línea, la static_assert, muestra que es un error en tiempo de compilación para asignar un tipo que no tiene ninguna coincidencia en todos. El compilador ya sabe que esto es un error y lo detiene.

    La biblioteca no convertir implícitamente el tipo subyacente, ya que esto puede causar muchas situaciones en las que tratas de evitar algo, pero esto ocurre debido a las conversiones. Permite la conversión explícita.

    • Que es increíble trabajo! Considero que el uso de la biblioteca en mi código. Sin embargo, ya que el impacto de hacerlo sería pesado, estoy un poco preocupado, ¿qué sucede en caso de que usted está obligado a abandonar el desarrollo de la biblioteca. ¿Has considerado la adición a boost o algo para asegurarse de que la biblioteca se mantiene por más de una persona?
    • He estado trabajando en conseguir que los conceptos y tipos de clase como no-tipo de los parámetros de las plantillas en C++ en una forma utilizable por mi biblioteca antes de ponerlo en algún lugar que la gente tenga una expectativa de estabilidad. Afortunadamente, tenemos ambos en C++20. Estoy a la espera de cualquiera de gcc para corregir algunos errores cuando se bloquea cuando la compilación de mi biblioteca, o para clang para integrar los conceptos de la rama. En ese punto, voy a mirar en la presentación a una colección más grande (lo más probable boost).
    • Ok, gracias por la información. Por favor déjenos saber (tal vez por comentario aquí) si usted considera que su trabajo estable y aplicable.
  5. 2

    Esto es en realidad un asunto complejo y que he abordado por un tiempo…

    Ahora tengo un públicamente disponibles de la biblioteca que le permitirá limitar el flotante y puntos enteros en el código para que pueda hacer más seguro de que son válidos en todo tiempo.

    No sólo eso, usted puede desactivar los límites en su versión final y eso significa que los tipos prácticamente se convierten en la misma como un typedef.

    Definir su tipo como:

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

    Y cuando no se define el CONTROLLED_VARS_DEBUG y CONTROLLED_VARS_LIMITED banderas, se obtiene prácticamente el mismo que este:

    typedef float angle_t;

    Estas clases se generan de modo que incluyen todos la necesaria operadores para no sufrir demasiado cuando el uso de ellos. Eso significa que usted puede ver su angle_t casi como un float.

    angle_t a;
    a += 35;

    Funcionará como se esperaba (y tirar si a + 35 > 360).

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

    Yo sé que esto fue publicado en el 2008… pero no veo ninguna buena enlace a una biblioteca que ofrece esta funcionalidad!?

  6. 1

    Por el momento, que es imposible en un portátil de manera debido a la de C++ reglas sobre cómo los métodos (y, por extensión, los constructores) son llamados con el constante argumentos.

    En el C++0x estándar, usted podría tener un const-expr que permita este tipo de error se produce a pesar de que.

    (Esto es suponiendo que usted desea producir un error sólo si el valor real es ilegal. Si los intervalos no coinciden, usted puede lograr esto)

  7. 1

    Una cosa para recordar acerca de las plantillas es que cada invocación de un único conjunto de parámetros de la plantilla va a acabar generando un «único» de la clase para que las comparaciones y las asignaciones generará un error de compilación. Puede haber algunos meta-programación de los gurus que podría saber cómo trabajar alrededor de esto, pero yo no soy uno de ellos. Mi enfoque sería para implementar estas en una clase con comprobaciones en tiempo de ejecución y sobrecargado de comparación y operadores de asignación.

  8. 1

    Me gustaría ofrecer una versión alternativa para Kasprzol la solución: El método propuesto utiliza siempre los límites de tipo int. Usted puede conseguir un poco más de flexibilidad y el tipo de seguridad con una aplicación como esta:

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

    Esto permitirá que el tipo de corrector para coger obvio miss tareas tales como:

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

    Sin embargo, la más avanzada de las relaciones en las que desea comprobar si el rango de una plantilla de ejemplo se incluye dentro de la gama de otra instancia no puede ser expresado en las plantillas de C++ mecanismo.

    Cada Delimitada especificación se convierte en un nuevo tipo. Por lo tanto el compilador puede comprobar el tipo de desajustes. No busque más avanzadas de las relaciones que podrían existir para esos tipos.

    • Esta propuesta de solución solo funciona para int y de tipo integral que los intervalos de plantilla de parametrizarse en el valor de cualquier otro tipo no funciona (no se puede parametrizar una plantilla en un flotador por ejemplo).
  9. 1

    Escribí una clase de C++ que imita la funcionalidad de Ada range.

    Se basa en plantillas, de forma similar a las soluciones que se proporcionan aquí.

    Si algo como esto es para ser utilizado en un proyecto real, que será utilizado en una manera muy elemental. Pequeños errores o malentendidos pueden ser desastrosos.

    Por lo tanto, aunque se trata de una pequeña biblioteca sin un montón de código, en mi opinión disposición de la unidad de pruebas y clara filosofía de diseño son muy importantes.

    No dude en probarlo y por favor dígame si usted encuentra cualquier problema.

    https://github.com/alkhimey/ConstrainedTypes

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

Dejar respuesta

Please enter your comment!
Please enter your name here