¿Por qué se usa el operador ternario para definir 1 y 0 en una macro?

79

Estoy usando un SDK para un proyecto integrado. En este código fuente encontré un código que al menos me pareció peculiar. En muchos lugares del SDK hay un código fuente en este formato:

#define ATCI_IS_LOWER( alpha_char )  ( ( (alpha_char >= ATCI_char_a) && (alpha_char <= ATCI_char_z) ) ? 1 : 0 )

#define ATCI_IS_UPPER( alpha_char )  ( ( (alpha_char >= ATCI_CHAR_A) && (alpha_char <= ATCI_CHAR_Z) ) ? 1 : 0 )

¿El uso del operador ternario aquí hace alguna diferencia?

No es

#define FOO (1 > 0)

lo mismo que

#define BAR ( (1 > 0) ? 1 : 0)

?

Intenté evaluarlo usando

printf("%d", FOO == BAR);

y obtiene el resultado 1, por lo que parece que son iguales. ¿Hay alguna razón para escribir el código como lo hicieron?

Viktor S
fuente
8
No, no hay ninguna razón. Tienes razón.
Art
29
Parcialmente fuera de tema: ¿Cuándo se detiene la locura de usar el preprocesador? Existe una posible evaluación múltiple de las funciones involucradas aquí. Simplemente innecesario.
stefan
3
A veces también es bueno ser explícito. El operador ternario aquí deja claro de un vistazo que el propósito de la macro es devolver un booleano.
tubería
5
Como mínimo, las macros deberían usarse en (alpha_char)lugar de alpha_char, solo para asegurarse de que no se rompa si alguien intenta algo loco comoATCI_IS_LOWER(true || -1) .
Justin Time - Reincorpora a Monica
5
Parece el tipo de CI que escribí hace mucho tiempo. Que había llegado a C de Pascal, que tenía un dedicado booleantipo, por lo que el tiempo perdido no contada cambiar horrores como if (n)a if (0 != n), probablemente, la adición de un elenco dudosa "asegurarse". Estoy seguro de que también supere las desigualdades a prueba de balas if (a < b) .... Seguro que se parecía al de Pascal if a < b then ..., pero sabía que C < no era un booleanpero un int, ¡y un intpodría ser casi cualquier cosa ! El miedo lleva al chapado en oro, el chapado en oro conduce a la paranoia, la paranoia conduce a ... un código como ese.
Kevin J. Chase

Respuestas:

131

Tienes razón, en C es tautólogo. Tanto su ternaria en particular condicional y (1 > 0) son de tipo int.

Pero sería importante en C ++, sin embargo, en algunos casos curiosos (por ejemplo, como parámetros para funciones sobrecargadas), ya que su expresión condicional ternaria es de tipo int, mientras que (1 > 0)es de tipo bool.

Supongo que el autor ha pensado un poco en esto, con miras a preservar la compatibilidad con C ++.

Betsabé
fuente
2
Pensé que las bool <-> intconversiones están implícitas en C ++ por §4.7 / 4 del estándar (conversión integral), entonces, ¿qué importancia tendría?
Motun
70
Considere dos sobrecargas de una función foo, una tomando a const bool&la otra tomando a const int&. Uno de ellos le paga, el otro formatea su disco duro. Es posible que desee asegurarse de que está llamando a la sobrecarga correcta en ese caso.
Bathsheba
3
¿No sería más obvio manejar este caso lanzando el resultado a en intlugar de usar el ternario?
martinkunev
18
@Bathsheba Si bien es un caso legítimo, cualquier programador que utilice sobrecargas integrales para implementar un comportamiento tan inconsistente es completamente malvado.
JAB
7
@JAB: No tienes que ser malvado, solo tienes que cometer el error (común) de escribir un fragmento de código que accidentalmente hace dos cosas diferentes (o peor aún, invocar un comportamiento indefinido ) dependiendo del tipo integral, y tener el la desgracia de hacerlo en un lugar que puede desencadenar rutas de código radicalmente diferentes.
28

Hay herramientas de linting que opinan que el resultado de una comparación es booleano y no se puede usar directamente en aritmética.

No es para nombrar nombres o señalar con el dedo, pero PC-lint es una herramienta muy interesante .

No digo que tengan razón, pero es una posible explicación de por qué el código se escribió así.

relajarse
fuente
10
Not to name names or point any fingers,pero hiciste ambas cosas, lol.
StackOverflowed
19

A veces verá esto en un código muy antiguo , de antes de que existiera un estándar C para deletrear que se (x > y)evalúa como 1 o 0 numérico; algunas CPU prefieren hacer que la evaluación sea -1 o 0 en su lugar, y algunos compiladores muy antiguos pueden haberlo seguido, por lo que algunos programadores sintieron que necesitaban una actitud defensiva adicional.

A veces también verá esto porque expresiones similares no necesariamente se evalúan como 1 o 0 numérico. Por ejemplo, en

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) ? 1 : 0)

la &expresión interna se evalúa como 0 o el valor numérico de F_DO_GRENFELZ, que probablemente no es 1, por lo que ? 1 : 0sirve para canonicalizarlo. Personalmente, creo que es más claro escribir eso como

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) != 0)

pero la gente razonable puede estar en desacuerdo. Si tuviera un montón de estos en una fila, probando diferentes tipos de expresiones, alguien podría haber decidido que era más fácil de mantener al ? 1 : 0final de todos ellos que preocuparse por cuáles realmente lo necesitaban.

zwol
fuente
En general, prefiero usar !!( expr )para canonizar un booleano, pero admitiré que es confuso si no está familiarizado con él.
PJTraill
1
@PJTraill Cada vez que pones espacios en el interior de tus paréntesis, Dios mata a un gatito. Por favor. Piense en los gatitos.
zwol
Esa es la mejor razón que he escuchado para no poner espacios entre corchetes en un programa en C.
PJTraill
15

Hay un error en el código del SDK, y el ternario probablemente fue una torpeza para solucionarlo.

Al ser una macro, los argumentos (alpha_char) pueden ser cualquier expresión y deben estar entre paréntesis porque expresiones como 'A' && 'c' fallarán la prueba.

#define IS_LOWER( x ) ( ( (x >= 'a') && (x <= 'z') ) ?  1 : 0 )
std::cout << IS_LOWER('A' && 'c');
**1**
std::cout << IS_LOWER('c' && 'A');
**0**

Es por eso que siempre se deben poner entre paréntesis los argumentos macro en la expansión.

Entonces, en su ejemplo (pero con parámetros), ambos tienen errores.

#define FOO(x) (x > 0)
#define BAR(x) ((x > 0) ? 1 : 0)

Serían reemplazados más correctamente por

#define BIM(x) ((x) > 0)

@CiaPan Hace un gran punto en el siguiente comentario, que es que usar un parámetro más de una vez conduce a resultados indefinibles. Por ejemplo

#define IS_LOWER( x ) (((x) >= 'a') && ((x) <= 'z'))
char ch = 'y';
std::cout << IS_LOWER(ch++);
**1** 
**BUT ch is now '{'**
Konchog
fuente
4
Otro error es que el parámetro se usa dos veces, por lo que un argumento con efectos secundarios conducirá a resultados impredecibles: IS_LOWER(++ var)puede incrementarse varuna o dos veces, además, puede que no note y reconozca minúsculas 'z'si varestaba 'y'antes de la llamada a la macro. Es por eso que deben evitarse tales macros, o simplemente reenviar el argumento a una función.
CiaPan
5

En C no importa. Las expresiones booleanas en C tienen un tipo inty un valor que es 0o 1, entonces

ConditionalExpr ? 1 : 0

no tiene efecto.

En C ++, es efectivamente una conversión a int, porque las expresiones condicionales en C ++ tienen tipo bool.

#include <stdio.h>
#include <stdbool.h>

#ifndef __cplusplus

#define print_type(X) _Generic(X, int: puts("int"), bool: puts("bool") );

#else
template<class T>
int print_type(T const& x);
template<> int print_type<>(int const& x) { return puts("int"); }
template<> int print_type<>(bool const& x) { return puts("bool"); }


#endif

int main()
{
    print_type(1);
    print_type(1 > 0);
    print_type(1 > 0 ? 1 : 0);

/*c++ output:
  int 
  int 
  int

  cc output:
  int
  bool
  int
*/

}

También es posible que no se pretendiera ningún efecto, y el autor simplemente pensó que aclaraba el código.

PSkocik
fuente
Por cierto, creo que C debería seguir suite y hacer expresiones booleanas _Bool, ahora que C tiene _Booly _Generic. No debería romper mucho código dado que todos los tipos más pequeños se autopromocionan inten la mayoría de los contextos de todos modos.
PSkocik
5

Una explicación simple es que algunas personas no comprenden que una condición devolvería el mismo valor en C o piensan que es más limpio escribir ((a>b)?1:0).

Eso explica por qué algunos también usan construcciones similares en lenguajes con booleanos adecuados, que en la sintaxis C sería (a>b)?true:false).

Esto también explica por qué no debería cambiar innecesariamente esta macro.

Hans Olsson
fuente
0

Quizás, al ser un software integrado, daría algunas pistas. Tal vez haya muchas macros escritas con este estilo, para indicar fácilmente que las líneas ACTI usan lógica directa en lugar de lógica invertida.

J.Guarin
fuente