Operador XOR lógico en C ++?

292

¿Hay tal cosa? Es la primera vez que encuentro una necesidad práctica, pero no veo una lista en Stroustrup . Tengo la intención de escribir:

// Detect when exactly one of A,B is equal to five.
return (A==5) ^^ (B==5);

Pero no hay ^^operador. ¿Puedo usar bitwise ^aquí y obtener la respuesta correcta (independientemente de la representación de máquina de verdadero y falso)? Nunca mezclo &y &&, o |y ||, así que dudo en hacer eso con ^y ^^.

Me sentiría más cómodo escribiendo mi propia bool XOR(bool,bool)función en su lugar.

RAC
fuente
57
En realidad, Jim, esa no es la única diferencia entre & y &&, por ejemplo ... 1 && 2 es cierto. pero 1 & 2 => 0. Por eso, creo que "cortocircuito" es solo una propiedad que tienen. La lógica es la característica más importante ...
Brian Postow
66
Sin mencionar que 2 && 3 == verdadero, pero 2 & 3 == 2.
David Thornley
1
David Thomley: Bueno, sí, pero 2 ==> cierto, así que está bien ... Recuerda, realmente no hay ningún booleano ...
Brian Postow el
13
@BrianPostow: En realidad, en C ++, los hay.
Adrian Willenbücher
77
Como se publica a continuación, aquí está la respuesta de Dennis Ritchie de por qué no existe: c-faq.com/misc/xor.dmr.html
Tobia

Respuestas:

536

El !=operador cumple este propósito para los boolvalores.

Greg Hewgill
fuente
77
Pero falso! = Falso => ​​falso
Jonas
13
Tenga en cuenta que esto solo funciona para booleanos. Y ^ funcionaría perfectamente bien allí. 2! = 1 => 1 que no es lo que quieres! como dice LiraNuna, poniendo un! enfrente de ambos lados resuelve ese problema. pero de nuevo, entonces puedes usar bitwise ^ ...
Brian Postow el
8
Correcto, tuve cuidado de mencionar "para los boolvalores" porque no necesariamente hace lo que puede desear para los no booleanos. Y como se trata de C ++, existe un booltipo real en lugar de tener que usarlo intpara ese propósito.
Greg Hewgill el
29
Si quieres hacerlo por tipo asolo escribe!(a) != !(a)
Chris Lutz
66
@ChrisLutz: sí, pero tenga cuidado con los operadores sobrecargados.
rwong
246

Para una verdadera operación XOR lógica, esto funcionará:

if(!A != !B) {
    // code here
}

Tenga en cuenta que !están allí para convertir los valores en booleanos y negarlos, de modo que dos enteros positivos desiguales (cada uno a true) se evaluarían false.

LiraNuna
fuente
66
¡No entiendo por qué se niegan A y B!
Martin Hansen
26
Principalmente para convertirlos a booleanos. !!funcionaría solo preguntar bien, pero como necesitan ser diferentes, negarlos no hace daño.
LiraNuna
44
La pregunta es si los compiladores pueden optimizar esto correctamente.
einpoklum
66
No saber la importancia de normalizar los bools me costó 2 días.
Makan Tayebi
99
@LiraNuna, "¡por qué se niegan A y B!" / "Principalmente para convertirlos a booleanos". - Creo que vale la pena mencionar esto en la respuesta.
cp.engr
42

La implementación adecuada de XOR lógico manual depende de cuán estrechamente desee imitar el comportamiento general de otros operadores lógicos ( ||y &&) con su XOR. Hay dos cosas importantes sobre estos operadores: 1) garantizan la evaluación de cortocircuito, 2) introducen un punto de secuencia, 3) evalúan sus operandos solo una vez.

La evaluación XOR, como comprenderá, no se puede cortocircuitar ya que el resultado siempre depende de ambos operandos. Entonces 1 está fuera de cuestión. ¿Pero qué hay de 2? Si no le importan 2, entonces, con boolvalores normalizados (es decir , el operador) !=hace el trabajo de XOR en términos del resultado. Y los operandos se pueden normalizar fácilmente con unario !, si es necesario. Por lo tanto, !A != !Bimplementa el XOR adecuado en ese sentido.

Pero si le importa el punto de secuencia adicional, ni la manera adecuada de implementar XOR es !=ni bit a bit ^. Una posible forma de hacer XOR (a, b) correctamente podría tener el siguiente aspecto

a ? !b : b

En realidad, esto es lo más cerca que puede llegar a hacer un XOR casero "similar" a ||y &&. Esto solo funcionará, por supuesto, si implementa su XOR como una macro. Una función no funcionará, ya que la secuencia no se aplicará a los argumentos de la función.

Sin embargo, alguien podría decir que la única razón de tener un punto de secuencia en cada uno &&y ||es apoyar la evaluación de cortocircuito, y por lo tanto, XOR no necesita uno. Esto tiene sentido, en realidad. Sin embargo, vale la pena considerar tener un XOR con un punto de secuencia en el medio. Por ejemplo, la siguiente expresión

++x > 1 && x < 5

tiene un comportamiento definido y un resultado específico en C / C ++ (con respecto a la secuencia al menos). Entonces, uno podría esperar razonablemente lo mismo de XOR lógico definido por el usuario , como en

XOR(++x > 1, x < 5)

mientras que un !=XOR basado en no tiene esta propiedad.

Hormiga
fuente
1
Te estás perdiendo la otra cosa importante sobre ||y &&: C) evalúan los operandos en un contexto booleano. Es decir, 1 && 2es cierto, a diferencia de lo 1 & 2que es cero. Del mismo modo, un ^^operador podría ser útil para proporcionar esta característica adicional, de evaluar los operandos en un contexto booleano. Por ejemplo, 1 ^^ 2es falso (a diferencia 1 ^ 2).
Craig McQueen
2
@ Craig McQueen: no me lo estoy perdiendo. El segundo párrafo de mi publicación lo menciona. En mi opinión, tratar los operandos como valores booleanos no es una característica crítica de los operadores lógicos, en el sentido de que no se introducirían solo por esa razón. La razón principal por la que se introdujeron es la evaluación en cortocircuito y el punto de secuencia requerido para eso.
AnT
Hoy en día, ¿su sugerencia solo funcionaría con una macro? Aunque es cierto que el orden de los parámetros que se evaluarán en una función depende del compilador, ¿no es raro que difiera de izquierda a derecha? Además, vale la pena señalar aquí en los comentarios que si una implementación se ve así #define XOR(ll,rr) { ll ? !rr : rr }, una llamada como int x = 2; XOR(++x > 1, x < 5);dará el resultado incorrecto. La llamada tendría que tener paréntesis adicionales, como en int x = 2; XOR( (++x > 1), (x < 5) );, con el fin de dar el resultado esperado correcta.
RAs
1
Como XOR no puede cortocircuitar, no hay necesidad de un punto de secuencia. XOR es más como + en ese sentido. A menos que también desee argumentar que (++ x) + x sea igual a 2x + 1, el punto de secuencia no es razonable.
hkBst
1
@hkBst: Creo que esto está completamente cubierto en la segunda parte de mi respuesta.
ANT
25

Hay otra forma de hacer XOR:

bool XOR(bool a, bool b)
{
    return (a + b) % 2;
}

Que obviamente se puede demostrar que funciona a través de:

#include <iostream>

bool XOR(bool a, bool b)
{
    return (a + b) % 2;
}

int main()
{
    using namespace std;
    cout << "XOR(true, true):\t" << XOR(true, true) << endl
         << "XOR(true, false):\t" << XOR(true, false) << endl
         << "XOR(false, true):\t" << XOR(false, true) << endl
         << "XOR(false, false):\t" << XOR(false, false) << endl
         << "XOR(0, 0):\t\t" << XOR(0, 0) << endl
         << "XOR(1, 0):\t\t" << XOR(1, 0) << endl
         << "XOR(5, 0):\t\t" << XOR(5, 0) << endl
         << "XOR(20, 0):\t\t" << XOR(20, 0) << endl
         << "XOR(6, 6):\t\t" << XOR(5, 5) << endl
         << "XOR(5, 6):\t\t" << XOR(5, 6) << endl
         << "XOR(1, 1):\t\t" << XOR(1, 1) << endl;
    return 0;
}
Bertie Wheen
fuente
77
Este enfoque puede generar un código bastante lento: 3-5 veces más lento que (! A)! = (! B) en clang y gcc usando libstdc ++: quick-bench.com/xtv2StFkR8PCkV4fOiqSgeG1T4Q
xaxxon
18

El operador XOR no se puede cortocircuitar; es decir, no puede predecir el resultado de una expresión XOR simplemente evaluando su operando izquierdo. Por lo tanto, no hay razón para proporcionar una ^^versión.

Mehrdad Afshari
fuente
25
-1 porque la principal diferencia entre && y & no es solo el cortocircuito. 1 && 2 es verdadero, pero 1 & 2 es falso. El cortocircuito es solo un efecto secundario útil.
Brian Postow el
66
La respuesta no está hablando &&y &en absoluto. Su punto es que no hay razón para presentar ^^. Sospecho que la propiedad que ^^consideraría cualquier valor no nulo 1no es realmente útil. O al menos no veo ningún uso.
Johannes Schaub - litb
66
También C ++! = Otros lenguajes. En C y C ++ como se muestra arriba, el cortocircuito no es solo algo agradable de tener, sino que es fundamentalmente importante. :)
Johannes Schaub - litb
13
Entonces deberías leer la respuesta de Dennis Ritchie a por qué no existe: it.usyd.edu.au/~dasymond/mirror/c-faq/misc/xor.dmr.html
Johannes Schaub - litb
55
Aquí hay un enlace de trabajo sobre la respuesta de Dennis Ritchie de por qué no existe: c-faq.com/misc/xor.dmr.html
Tobia
13

Hubo un buen código publicado que resolvió el problema mejor que! A! =! B

Tenga en cuenta que tuve que agregar BOOL_DETAIL_OPEN / CLOSE para que funcione en MSVC 2010

/* From: http://groups.google.com/group/comp.std.c++/msg/2ff60fa87e8b6aeb

   Proposed code    left-to-right?  sequence point?  bool args?  bool result?  ICE result?  Singular 'b'?
   --------------   --------------  ---------------  ---------- ------------  -----------  -------------
   a ^ b                  no              no             no          no           yes          yes
   a != b                 no              no             no          no           yes          yes
   (!a)!=(!b)             no              no             no          no           yes          yes
   my_xor_func(a,b)       no              no             yes         yes          no           yes
   a ? !b : b             yes             yes            no          no           yes          no
   a ? !b : !!b           yes             yes            no          no           yes          no
   [* see below]          yes             yes            yes         yes          yes          no
   (( a bool_xor b ))     yes             yes            yes         yes          yes          yes

   [* = a ? !static_cast<bool>(b) : static_cast<bool>(b)]

   But what is this funny "(( a bool_xor b ))"? Well, you can create some
   macros that allow you such a strange syntax. Note that the
   double-brackets are part of the syntax and cannot be removed! The set of
   three macros (plus two internal helper macros) also provides bool_and
   and bool_or. That given, what is it good for? We have && and || already,
   why do we need such a stupid syntax? Well, && and || can't guarantee
   that the arguments are converted to bool and that you get a bool result.
     Think "operator overloads". Here's how the macros look like:

   Note: BOOL_DETAIL_OPEN/CLOSE added to make it work on MSVC 2010
  */

#define BOOL_DETAIL_AND_HELPER(x) static_cast<bool>(x):false
#define BOOL_DETAIL_XOR_HELPER(x) !static_cast<bool>(x):static_cast<bool>(x)

#define BOOL_DETAIL_OPEN (
#define BOOL_DETAIL_CLOSE )

#define bool_and BOOL_DETAIL_CLOSE ? BOOL_DETAIL_AND_HELPER BOOL_DETAIL_OPEN
#define bool_or BOOL_DETAIL_CLOSE ? true:static_cast<bool> BOOL_DETAIL_OPEN
#define bool_xor BOOL_DETAIL_CLOSE ? BOOL_DETAIL_XOR_HELPER BOOL_DETAIL_OPEN
dados elegantes
fuente
6

Use un simple:

return ((op1 ? 1 : 0) ^ (op2 ? 1 : 0));
tiands
fuente
5

Así es como creo que escribes una comparación XOR en C ++:

bool a = true;   // Test by changing to true or false
bool b = false;  // Test by changing to true or false
if (a == !b)     // THIS IS YOUR XOR comparison
{
    // do whatever
}

Prueba

XOR TABLE
 a   b  XOR
--- --- ---
 T   T   F
 T   F   T
 F   T   T
 F   F   F

a == !b TABLE
 a   b  !b  a == !b
--- --- --- -------
 T   T   F     F
 T   F   T     T
 F   T   F     T
 F   F   T     F

La prueba es que un estudio exhaustivo de entradas y salidas muestra que en las dos tablas, para cada conjunto de entradas, el resultado es siempre el mismo en las dos tablas.

Por lo tanto, la pregunta original es cómo escribir:

return (A==5) ^^ (B==5)

La respuesta seria

return (A==5) == !(B==5);

O si quieres, escribe

return !(A==5) == (B==5);
Indinfer
fuente
! a! =! b parece ser mejor porque convierte tus argumentos en bool para ti.
xaxxon
3

(A || B) && !(A && B)

La primera parte es A OR B, que es el OR inclusivo; la segunda parte es, NO A Y B. Juntos, obtienes A o B, pero no tanto A como B.

Esto proporcionará el XOR probado en la tabla de verdad a continuación.

|-----|-----|-----------|
|  A  |  B  |  A XOR B  |
|-----|-----|-----------|
|  T  |  T  |   False   |
|-----|-----|-----------|
|  T  |  F  |   True    |
|-----|-----|-----------|
|  F  |  T  |   True    |
|-----|-----|-----------|
|  F  |  F  |   False   |
|-----|-----|-----------|
Comer en joes
fuente
No estoy cavando el rendimiento en este enfoque: quick-bench.com/PgNgGN8ATrKt7el1dAaJj7QtuF4
xaxxon
No hay necesidad de estar a la defensiva al respecto.
xaxxon
1
@xaxxon: ¿No ves el punto en estos puntos de referencia? En StringCreation usaste en la creación de cadenas, pero en el segundo punto de referencia no lo hiciste. Si coloca un código idéntico en ambos puntos de referencia y llama a diferentes XOR para cada punto de referencia (XOR y XOR2) obtendrá los mismos resultados de referencia. Entonces, ¿qué has estado tratando de decir?
SoLaR
1

Uso "xor" (parece que es una palabra clave; en Code :: Blocks al menos se pone en negrita) así como puedes usar "y" en lugar de &&y "o" en lugar de ||.

if (first xor second)...

Sí, es bit a bit. Lo siento.

csiz
fuente
2
Supongo que están ocultos #defines de alguna parte. Estoy bastante seguro de que "y" y "xor" no son palabras clave en ansi C ... (al menos no C79)
Brian Postow
1
@Brian Postow: No sé qué es C79, pero en C ++ 98 andy xorson macros de biblioteca estándar. No son de "algún lugar", son de <iso646.h>. Estas macros también están en C99 (no estoy seguro acerca de C89 / 90).
AnT
55
@ Brian Postow: ... xorsignifica bit a bit XOR embargo, mientras que andes lógico y.
ANT
1
Escribí mal C89 como C79 ... yy xor, etc.no están en mi copia de K&R Creo que nunca he usado iso686.h, al menos no a sabiendas ... y, por lo tanto, sí, son #defines de algún lado, simplemente sabes dónde está ese lugar B-)
Brian Postow
2
Ciertamente están en C99 usando ese encabezado. En C ++, están integrados en el lenguaje como "tokens alternativos", y puede hacerlo struct A { compl A() { } };para definir un destructor, por ejemplo.
Johannes Schaub - litb
0
#if defined(__OBJC__)
    #define __bool BOOL
    #include <stdbool.h>
    #define __bool bool
#endif

static inline __bool xor(__bool a, __bool b)
{
    return (!a && b) || (a && !b);
}

Funciona como se define. Los condicionales son para detectar si está utilizando Objective-C , que solicita BOOL en lugar de bool (¡la longitud es diferente!)

Maxthon Chan
fuente
3
Esto viola la regla del doble guión bajo.
Tamás Szelei
@ TamásSzelei No necesariamente, ya que el compilador no lo ve, ya que está preprocesado, y en el mundo de Objective-C, los subrayados dobles son bastante comunes.
Maxthon Chan
Buen punto sobre el preprocesador, aunque para mí todavía es olor a código de esta manera (¿por qué usar una macro en lugar de un typedef de todos modos?). Además, la pregunta no era sobre Objective-C.
Tamás Szelei
@ TamásSzelei Bueno, tenía la costumbre de compartir archivos de encabezado en varios idiomas, y generalmente todos los encabezados provienen de Objective-C. Mi nuevo código no huele demasiado ahora, pero el doble guión bajo todavía se usa de vez en cuando para cumplir con los hábitos de ObjC.
Maxthon Chan