Double Negation en C ++ code

Acabo de entrar en un proyecto con una base de código bastante grande.

En su mayoría trato con C ++ y gran parte del código que escriben usa doble negación para su lógica booleana.

if (!!variable && (!!api.lookup("some-string"))) { do_some_stuff(); } 

Sé que estos tipos son progtwigdores inteligentes, es obvio que no están haciendo esto por accidente.

No soy un experto en C ++ experimentado, mi única conjetura de por qué lo están haciendo es que quieren hacer absolutamente positivo que el valor que se evalúa es la representación booleana real. Entonces lo niegan, luego lo niegan nuevamente para volver a su valor booleano real.

¿Es esto correcto, o me estoy perdiendo algo?

Es un truco para convertir a bool.

En realidad, es una expresión muy útil en algunos contextos. Tome estas macros (ejemplo del kernel de Linux). Para GCC, se implementan de la siguiente manera:

 #define likely(cond) (__builtin_expect(!!(cond), 1)) #define unlikely(cond) (__builtin_expect(!!(cond), 0)) 

¿Por qué tienen que hacer esto? El __builtin_expect de GCC trata sus parámetros como long y no bool , por lo que debe haber alguna forma de conversión. Como no saben qué condiciones tienen cuando escriben esas macros, es más general simplemente usarlas. idioma.

Probablemente podrían hacer lo mismo al comparar contra 0, pero en mi opinión, en realidad es más sencillo hacer la doble negación, ya que eso es lo más cercano a un elenco a bool que C tiene.

Este código también se puede usar en C ++ … es una cosa del mínimo común denominador. Si es posible, haz lo que funciona tanto en C como en C ++.

Los codificadores piensan que convertirá el operando a bool, pero como los operandos de && ya están convertidos implícitamente en bool, es completamente redundante.

Sí, es correcto y no te falta algo. !! es una conversión a bool. Vea esta pregunta para más discusión.

Es una técnica para evitar escribir (variable! = 0), es decir, convertir de cualquier tipo que sea a un bool.

El código IMO como este no tiene cabida en los sistemas que deben mantenerse, porque no es un código inmediatamente legible (de ahí la pregunta, en primer lugar).

El código debe ser legible; de ​​lo contrario, deje un legado de deuda de tiempo para el futuro, ya que lleva tiempo entender algo que es innecesariamente intrincado.

Da un paso lateral a una advertencia del comstackdor. Prueba esto:

 int _tmain(int argc, _TCHAR* argv[]) { int foo = 5; bool bar = foo; bool baz = !!foo; return 0; } 

La línea ‘bar’ genera un “valor de forzado a bool ‘verdadero’ o ‘falso’ (advertencia de rendimiento)” en MSVC ++, pero la línea ‘baz’ se escabulle muy bien.

Es el operador! ¿sobrecargado?
Si no, probablemente estén haciendo esto para convertir la variable en un bool sin producir una advertencia. Esta definitivamente no es una forma estándar de hacer las cosas.

Los desarrolladores de Legacy C no tenían un tipo Boolean, por lo que a menudo #define TRUE 1 y #define FALSE 0 y luego usaron tipos de datos numéricos arbitrarios para las comparaciones booleanas. Ahora que tenemos bool , muchos comstackdores emitirán advertencias cuando se realicen ciertos tipos de asignaciones y comparaciones usando una combinación de tipos numéricos y booleanos. Estos dos usos eventualmente colisionarán cuando se trabaja con código heredado.

Para evitar este problema, algunos desarrolladores utilizan la siguiente identidad booleana !num_value devuelve bool true si num_value == 0 ; false contrario. !!num_value devuelve bool false si num_value == 0 ; true contrario. La única negación es suficiente para convertir num_value a bool ; sin embargo, la doble negación es necesaria para restaurar el sentido original de la expresión booleana.

Este patrón se conoce como una expresión idiomática , es decir, algo comúnmente utilizado por personas que están familiarizadas con el idioma. Por lo tanto, no lo veo como un antipatrón, tanto como lo haría static_cast(num_value) . El elenco podría dar los resultados correctos, pero algunos comstackdores emiten una advertencia de rendimiento, por lo que todavía tiene que abordar eso.

La otra forma de abordar esto es decir, (num_value != FALSE) . También estoy de acuerdo con eso, ¡en general !!num_value es mucho menos detallado, puede ser más claro y no confunde la segunda vez que lo ves.

Como mencionó Marcin , bien podría importar si la sobrecarga del operador está en juego. De lo contrario, en C / C ++ no importa, excepto si estás haciendo una de las siguientes cosas:

  • comparación directa con true (o en C algo así como una macro TRUE ), que casi siempre es una mala idea. Por ejemplo:

    if (api.lookup("some-string") == true) {...}

  • simplemente quiere algo convertido a un valor estricto 0/1. En C ++ una asignación a bool hará esto implícitamente (para aquellas cosas que son implícitamente convertibles a bool ). En C o si se trata de una variable no bool, esta es una expresión que he visto, pero prefiero la (some_variable != 0) .

Creo que en el contexto de una expresión booleana más grande simplemente complica las cosas.

Si la variable es de tipo de objeto, podría tener un! operador definido pero sin conversión a bool (o peor un lanzamiento implícito a int con semántica diferente. Llamar al operador! dos veces da como resultado una conversión a bool que funciona incluso en casos extraños.

!! se usó para hacer frente a C ++ original que no tenía un tipo booleano (como tampoco lo hizo C).


Problema de ejemplo:

Dentro de if(condition) , la condition necesita evaluar a algún tipo como double, int, void* , etc., pero no bool ya que aún no existe.

Supongamos que existe una clase int256 (un entero de 256 bits) y todas las conversiones / moldes enteros están sobrecargadas.

 int256 x = foo(); if (x) ... 

Para probar si x era “verdadero” o distinto de cero, if (x) convertiría x en un entero y luego evaluaría si ese int fuera distinto de cero. Una sobrecarga típica de (int) x devolvería solo los LSbits de x . if (x) estaba solo probando los LSbits de x .

Pero C ++ tiene el ! operador. Una !x sobrecargada normalmente evaluaría todos los bits de x . Entonces, regrese a la lógica no invertida if (!!x) se usa.

Ref. ¿Las versiones anteriores de C ++ usaban el operador `int` de una clase cuando evaluaban la condición en una instrucción` if () `?

Es correcto pero, en C, aquí no tiene sentido – ‘if’ y ‘&&’ tratarían la expresión de la misma manera sin el ‘!!’.

La razón para hacer esto en C ++, supongo, es que ‘&&’ podría estar sobrecargado. Pero entonces, también podría ‘!’, Por lo que realmente no garantiza que obtenga un bool, sin mirar el código de los tipos de variable y api.call . Tal vez alguien con más experiencia en C ++ podría explicar; quizás sea una medida de defensa en profundidad, no una garantía.

Tal vez los progtwigdores pensaban algo como esto …

!! myAnswer es booleano. En contexto, debería volverse booleano, pero me encanta golpear las cosas para asegurarme, porque alguna vez hubo un error misterioso que me mordió, y bang bang, lo maté.

Este puede ser un ejemplo del truco de doble explosión , ver The Safe Bool Idiom para más detalles. Aquí resumo la primera página del artículo.

En C ++ hay varias maneras de proporcionar pruebas booleanas para las clases.

Una forma obvia es el operator bool operador de conversión operator bool .

 // operator bool version class Testable { bool ok_; public: explicit Testable(bool b=true):ok_(b) {} operator bool() const { // use bool conversion operator return ok_; } }; 

Podemos probar la clase,

 Testable test; if (test) std::cout < < "Yes, test is working!\n"; else std::cout << "No, test is not working!\n"; 

Sin embargo, opereator bool se considera inseguro porque permite operaciones sin sentido como la test < < 1; o int i=test .

Usando el operator! es más seguro porque evitamos problemas de conversión implícita o sobrecarga.

La implementación es trivial,

 bool operator!() const { // use operator! return !ok_; } 

Las dos formas idiomáticas para probar el objeto Testable son

  Testable test; if (!!test) std::cout < < "Yes, test is working!\n"; if (!test2) { std::cout << "No, test2 is not working!\n"; 

La primera versión de if (!!test) es lo que algunas personas llaman el truco de doble explosión .