una resueltos recorrer manejo listas lista ejercicios drracket definir c++ c enums

c++ - resueltos - manejo de listas en scheme



Enumeraciones C/C++: detectar cuando varios elementos se asignan al mismo valor (7)

Hay un par de maneras de verificar este tiempo de compilación, pero es posible que no siempre funcionen para usted. Comience por insertar un valor de enumeración "marcador" justo antes de MsgFoo2A.

typedef enum { MsgFoo1A = BASE1_VAL, MsgFoo1B, MsgFoo1C, MsgFoo1D, MsgFoo1E, MARKER_1_DONT_USE, /* Don''t use this value, but leave it here. */ MsgFoo2A = BASE2_VAL, MsgFoo2B } FOO;

Ahora necesitamos una forma de garantizar que MARKER_1_DONT_USE < BASE2_VAL en tiempo de compilación. Hay dos técnicas comunes.

Matrices de tamaño negativo

Es un error declarar una matriz con tamaño negativo. Esto se ve un poco feo, pero funciona.

extern int IGNORE_ENUM_CHECK[MARKER_1_DONT_USE > BASE2_VAL ? -1 : 1];

Casi todos los compiladores que se hayan escrito generarán un error si MARKER_1_DONT_USE es mayor que BASE_2_VAL. GCC escupe:

test.c:16: error: size of array ‘IGNORE_ENUM_CHECK’ is negative

Aserciones estáticas

Si su compilador es compatible con C11, puede usar _Static_assert . El soporte para C11 no es omnipresente, pero su compilador puede soportar _Static_assert todos modos, especialmente dado que la característica correspondiente en C ++ es ampliamente compatible.

_Static_assert(MARKER_1_DONT_USE < BASE2_VAL, "Enum values overlap.");

GCC escupe el siguiente mensaje:

test.c:16:1: error: static assertion failed: "Enum values overlap." _Static_assert(MARKER_1_DONT_USE < BASE2_VAL, "Enum values overlap."); ^

¿Hay alguna forma de tiempo de compilación para detectar / prevenir valores duplicados dentro de una enumeración C / C ++?

El problema es que hay varios elementos que se inicializan a valores explícitos .

Fondo:

Heredé un código C como el siguiente:

#define BASE1_VAL (5) #define BASE2_VAL (7) typedef enum { MsgFoo1A = BASE1_VAL, // 5 MsgFoo1B, // 6 MsgFoo1C, // 7 MsgFoo1D, // 8 MsgFoo1E, // 9 MsgFoo2A = BASE2_VAL, // Uh oh! 7 again... MsgFoo2B // Uh oh! 8 again... } FOO;

El problema es que a medida que el código crece y los desarrolladores agregan más mensajes al grupo MsgFoo1x , eventualmente sobrepasa BASE2_VAL .

Este código eventualmente se migrará a C ++, de modo que si hay una solución solo para C ++ (¿magia de plantilla?), Está bien, pero una solución que funciona con C y C ++ es mejor.


No creo que haya una forma de detectar esto con el lenguaje en sí, considerando que hay casos imaginables en los que desearía que dos valores de enumeración fueran los mismos. Sin embargo, siempre puede asegurarse de que todos los elementos configurados explícitamente estén en la parte superior de la lista:

typedef enum { MsgFoo1A = BASE1_VAL, // 5 MsgFoo2A = BASE2_VAL, // 7 MsgFoo1B, // 8 MsgFoo1C, // 9 MsgFoo1D, // 10 MsgFoo1E, // 11 MsgFoo2B // 12 } FOO;

Siempre que los valores asignados estén en la parte superior, no es posible una colisión, a menos que por alguna razón las macros se expandan a valores que son los mismos.

Por lo general, este problema se supera dando un número fijo de bits para cada grupo MsgFooX, y asegurando que cada grupo no se desborde, se asigna un número de bits. La solución de "Número de bits" es agradable porque permite una prueba a nivel de bit para determinar a qué grupo de mensajes pertenece algo. Pero no hay una función de lenguaje incorporado para hacer esto porque hay casos legítimos para una enumeración que tiene dos del mismo valor:

typedef enum { gray = 4, //Gr[ae]y should be the same grey = 4, color = 5, //Also makes sense in some cases couleur = 5 } FOO;


No me gustó completamente ninguna de las respuestas ya publicadas aquí, pero me dieron algunas ideas. La técnica crucial es confiar en la respuesta de Ben Voight al usar una declaración de cambio. Si varios casos en un interruptor comparten el mismo número, obtendrá un error de compilación.

Lo más útil para mí y, probablemente, para el póster original, esto no requiere ninguna característica C ++.

Para aclarar las cosas, utilicé la respuesta de aaronps en ¿Cómo puedo evitar repetirme cuando creo una enum de C ++ y una estructura de datos dependiente?

Primero, defina esto en algún encabezado en algún lugar:

#define DEFINE_ENUM_VALUE(name, value) name=value, #define CHECK_ENUM_VALUE(name, value) case name: #define DEFINE_ENUM(enum_name, enum_values) / typedef enum { enum_values(DEFINE_ENUM_VALUE) } enum_name; #define CHECK_ENUM(enum_name, enum_values) / void enum_name ## _test (void) { switch(0) { enum_values(CHECK_ENUM_VALUE); } }

Ahora, cada vez que necesite tener una enumeración:

#define COLOR_VALUES(GEN) / GEN(Red, 1) / GEN(Green, 2) / GEN(Blue, 2)

Finalmente, estas líneas son necesarias para hacer realmente la enumeración:

DEFINE_ENUM(Color, COLOR_VALUES) CHECK_ENUM(Color, COLOR_VALUES)

DEFINE_ENUM el tipo de datos enum mismo. CHECK_ENUM realiza una función de prueba que activa todos los valores enum. El compilador se bloqueará al compilar CHECK_ENUM si tiene duplicados.


No sé nada que verifique automáticamente todos los miembros de enum, pero si desea comprobar que los cambios futuros en los inicializadores (o las macros en las que confían) no causan colisiones:

switch (0) { case MsgFoo1A: break; case MsgFoo1B: break; case MsgFoo1C: break; case MsgFoo1D: break; case MsgFoo1E: break; case MsgFoo2A: break; case MsgFoo2B: break; }

causará un error de compilación si se reutiliza alguno de los valores integrales, y la mayoría de los compiladores incluso le dirán qué valor (el valor numérico) fue un problema.


No vi "bonita" en sus requisitos, por lo que presento esta solución implementada utilizando la biblioteca Boost Preprocessor.

Como un descargo de responsabilidad inicial, no he usado Boost.Preprocessor mucho y solo he probado esto con los casos de prueba presentados aquí, por lo que podría haber errores, y puede haber una forma más fácil y más clara de hacerlo. . Ciertamente, recibo comentarios, correcciones, sugerencias, insultos, etc.

Aquí vamos:

#include <boost/preprocessor.hpp> #define EXPAND_ENUM_VALUE(r, data, i, elem) / BOOST_PP_SEQ_ELEM(0, elem) / BOOST_PP_IIF( / BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2), / = BOOST_PP_SEQ_ELEM(1, elem), / BOOST_PP_EMPTY()) / BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(data, BOOST_PP_ADD(i, 1))) #define ADD_CASE_FOR_ENUM_VALUE(r, data, elem) / case BOOST_PP_SEQ_ELEM(0, elem) : break; #define DEFINE_UNIQUE_ENUM(name, values) / enum name / { / BOOST_PP_SEQ_FOR_EACH_I(EXPAND_ENUM_VALUE, / BOOST_PP_SEQ_SIZE(values), values) / }; / / namespace detail / { / void UniqueEnumSanityCheck##name() / { / switch (name()) / { / BOOST_PP_SEQ_FOR_EACH(ADD_CASE_FOR_ENUM_VALUE, name, values) / } / } / }

Entonces podemos usarlo así:

DEFINE_UNIQUE_ENUM(DayOfWeek, ((Monday) (1)) ((Tuesday) (2)) ((Wednesday) ) ((Thursday) (4)))

El valor del enumerador es opcional; este código genera una enumeración equivalente a:

enum DayOfWeek { Monday = 1, Tuesday = 2, Wednesday, Thursday = 4 };

También genera una función de comprobación de cordura que contiene una declaración de cambio como se describe en la respuesta de Ben Voigt . Si cambiamos la declaración de enumeración de modo que tengamos valores de enumerador no únicos, por ejemplo,

DEFINE_UNIQUE_ENUM(DayOfWeek, ((Monday) (1)) ((Tuesday) (2)) ((Wednesday) ) ((Thursday) (1)))

no compilará (Visual C ++ informa el error esperado C2196: valor de caso ''1'' ya utilizado ).

Gracias también a Matthieu M., cuya respuesta a otra pregunta me interesó en la biblioteca Boost Preprocessor.


Podrías rodar una solución más robusta de definir enumeraciones usando Boost.Preprocessor - si vale la pena el tiempo es algo diferente.

Si te mudas a C ++ de todos modos, tal vez Boost.Enum (propuesto) te convenga (disponible a través de Boost Vault ).

Otro enfoque podría ser usar algo como gccxml (o más cómodamente pygccxml ) para identificar candidatos para la inspección manual.


Si bien no tenemos plena reflexión, puede resolver este problema si puede volver a enumerar los valores de enumeración.

En algún lugar esto es declarado:

enum E { A = 0, B = 0 };

en otro lugar, construimos esta maquinaria:

template<typename S, S s0, S... s> struct first_not_same_as_rest : std::true_type {}; template<typename S, S s0, S s1, S... s> struct first_not_same_as_rest : std::integral_constant< bool, (s0 != s1) && first_not_same_as_rest< S, s0, s... >::value > {}; template<typename S, S... s> struct is_distinct : std::true_type {}; template<typename S, S s0, S... s> struct is_distinct : std::integral_constant< bool, std::is_distinct<S, s...>::value && first_not_same_as_rest< S, s0, s... >::value > {};

Una vez que tenga esa maquinaria (que requiere C ++ 11), podemos hacer lo siguiente:

static_assert( is_distinct< E, A, B >::value, "duplicate values in E detected" );

y en tiempo de compilación nos aseguraremos de que no haya dos elementos iguales.

Esto requiere O (n) profundidad de recursión y O (n ^ 2) trabajo por el compilador en tiempo de compilación, por lo que para enums extremadamente grandes esto podría causar problemas. La profundidad de AO (lg (n)) y el trabajo de O (n lg (n)) con un factor constante mucho más grande se puede hacer ordenando primero la lista de elementos, pero eso es mucho, mucho más trabajo.

Con el código de reflexión enum propuesto para C ++ 1y-C ++ 17, esto será factible sin volver a contar los elementos.