Opérateur XOR logique en C ++?

292

Existe-t-il une telle chose? C'est la première fois que j'en rencontre un besoin pratique, mais je n'en vois aucun répertorié dans Stroustrup . J'ai l'intention d'écrire:

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

Mais il n'y a pas d' ^^opérateur. Puis-je utiliser le bit ^ici et obtenir la bonne réponse (quelle que soit la représentation machine de vrai et de faux)? Je ne mélange jamais &et &&, ou |et ||, j'hésite donc à le faire avec ^et ^^.

Je préfèrerais plutôt écrire ma propre bool XOR(bool,bool)fonction.

RAC
la source
57
En fait, Jim, ce n'est pas la seule différence entre & et && par exemple ... 1 && 2 est vrai. mais 1 & 2 => 0. Pour cette raison, je pense que le "court-circuit" est juste une propriété qu'ils possèdent. La logique est la caractéristique la plus importante ...
Brian Postow
6
Sans oublier que 2 && 3 == true, mais 2 & 3 == 2.
David Thornley
1
David Thomley: Eh bien, oui, mais 2 ==> vrai, donc ça va ... Rappelez-vous, il n'y a vraiment pas de booléens ...
Brian Postow
13
@BrianPostow: En fait, en C ++, il y en a.
Adrian Willenbücher
7
Comme indiqué ci-dessous, voici la réponse de Dennis Ritchie expliquant pourquoi elle n'existe pas: c-faq.com/misc/xor.dmr.html
Tobia

Réponses:

536

L' !=opérateur sert cet objectif pour les boolvaleurs.

Greg Hewgill
la source
7
Mais faux! = Faux => faux
Jonas
13
Notez que cela ne fonctionne que pour les booléens. Et ^ fonctionnerait parfaitement bien là-bas. 2! = 1 => 1 ce qui n'est pas ce que vous voulez! comme le dit LiraNuna, en mettant un! devant les deux côtés résout ce problème. mais encore une fois, alors vous pouvez utiliser au niveau du bit ^ ...
Brian Postow
8
À droite, j'ai pris soin de mentionner "pour les boolvaleurs" car cela ne fait pas nécessairement ce que vous pourriez souhaiter pour les non-booléens. Et comme il s'agit de C ++, il existe un vrai booltype au lieu de devoir l'utiliser intà cette fin.
Greg Hewgill
29
Si vous voulez le faire pour le type aécrivez simplement!(a) != !(a)
Chris Lutz
6
@ChrisLutz: oui, mais attention aux opérateurs surchargés.
rwong
246

Pour une véritable opération XOR logique, cela fonctionnera:

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

Notez que !sont là pour convertir les valeurs en booléens et les annuler, de sorte que deux entiers positifs inégaux (chacun a true) seraient évalués false.

LiraNuna
la source
6
Je ne comprends pas pourquoi A et B sont annulés!
Martin Hansen
26
Principalement pour les convertir en booléens. !!fonctionnerait juste demander bien, mais comme ils doivent être différents, les nier ne fait aucun mal.
LiraNuna
4
La question est de savoir si les compilateurs sont capables d'optimiser correctement cela.
einpoklum
6
Ne pas savoir l'importance de normaliser les bools m'a coûté 2 jours.
Makan Tayebi
9
@LiraNuna, "pourquoi A et B sont annulés!" / "Principalement pour les convertir en booléen." - Je pense que cela mérite d'être mentionné dans la réponse.
cp.engr
42

L' implémentation manuelle XOR logique appropriée dépend de la façon dont vous souhaitez imiter le comportement général des autres opérateurs logiques ( ||et &&) avec votre XOR. Il y a deux choses importantes à propos de ces opérateurs: 1) ils garantissent l'évaluation des courts-circuits, 2) ils introduisent un point de séquence, 3) ils évaluent leurs opérandes une seule fois.

L'évaluation XOR, comme vous le comprenez, ne peut pas être court-circuitée car le résultat dépend toujours des deux opérandes. Donc 1 est hors de question. Mais qu'en est-il de 2? Si vous ne vous souciez pas de 2, alors avec des boolvaleurs normalisées (c'est-à-dire ), l'opérateur !=fait le travail de XOR en termes de résultat. Et les opérandes peuvent être facilement normalisés avec unaire !, si nécessaire. Implémente ainsi !A != !Ble XOR approprié à cet égard.

Mais si vous vous souciez du point de séquence supplémentaire, !=ni le bit ni le bit ^n'est la bonne façon d'implémenter XOR. Une façon possible de faire XOR (a, b) correctement pourrait se présenter comme suit

a ? !b : b

Ceci est en fait aussi proche que possible de la fabrication d'un XOR maison "similaire" à ||et &&. Cela ne fonctionnera, bien sûr, que si vous implémentez votre XOR en tant que macro. Une fonction ne fonctionnera pas, car le séquencement ne s'appliquera pas aux arguments de la fonction.

Quelqu'un pourrait dire cependant que la seule raison d'avoir un point de séquence à chacun &&et ||est de prendre en charge l'évaluation en court-circuit, et donc XOR n'en a pas besoin. Cela a du sens, en fait. Pourtant, il vaut la peine d'envisager d'avoir un XOR avec un point de séquence au milieu. Par exemple, l'expression suivante

++x > 1 && x < 5

a un comportement défini et un résultat spécifique en C / C ++ (en ce qui concerne le séquençage au moins). Donc, on peut raisonnablement s’attendre à la même chose de la part du XOR logique défini par l’utilisateur , comme dans

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

tandis qu'un !=XOR basé sur n'a pas cette propriété.

Fourmi
la source
1
Vous manquez l'autre chose importante à propos ||et &&: C) , ils évaluent les opérandes dans un contexte booléen. C'est, 1 && 2est vrai, contrairement à 1 & 2ce qui est zéro. De même, un ^^opérateur pourrait être utile pour fournir cette fonctionnalité supplémentaire, d'évaluer les opérandes dans un contexte booléen. Par exemple, 1 ^^ 2c'est faux (contrairement 1 ^ 2).
Craig McQueen
2
@Craig McQueen: Je ne le manque pas. Le deuxième paragraphe de mon billet le mentionne. À mon avis, le traitement des opérandes comme des valeurs booléennes n'est pas une caractéristique critique des opérateurs logiques, en ce sens qu'ils ne seraient pas introduits pour cette seule raison. La principale raison pour laquelle ils ont été introduits est l'évaluation en court-circuit et le point de séquence requis pour cela.
2011
De nos jours, votre suggestion ne fonctionnerait-elle toujours qu'avec une macro? Bien qu'il soit vrai que l'ordre des paramètres à évaluer dans une fonction dépend du compilateur, n'est-il pas rare actuellement de différer de gauche à droite? En outre, il peut être utile de noter ici dans les commentaires que si une implémentation ressemble #define XOR(ll,rr) { ll ? !rr : rr }, un appel similaire int x = 2; XOR(++x > 1, x < 5);donnera le mauvais résultat. L'appel devrait avoir des parenthèses supplémentaires, comme dans int x = 2; XOR( (++x > 1), (x < 5) );, afin de donner le résultat attendu correct.
RAs
1
Étant donné que XOR ne peut pas court-circuiter, il n'est pas nécessaire d'avoir un point de séquence. XOR ressemble plus à + à cet égard. À moins que vous ne vouliez également plaider pour que (++ x) + x soit égal à 2x + 1, le point de séquence n'est pas raisonnable.
hkBst
1
@hkBst: Je pense que cela est entièrement couvert dans la deuxième partie de ma réponse.
AnT
25

Il existe une autre façon de faire XOR:

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

Ce qui peut évidemment être démontré pour fonctionner via:

#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
la source
7
Cette approche peut générer du code assez lent - 3-5x plus lent que (! A)! = (! B) à la fois sur clang et gcc en utilisant libstdc ++: quick-bench.com/xtv2StFkR8PCkV4fOiqSgeG1T4Q
xaxxon le
18

L'opérateur XOR ne peut pas être court-circuité; c'est-à-dire que vous ne pouvez pas prédire le résultat d'une expression XOR simplement en évaluant son opérande de gauche. Il n'y a donc aucune raison de fournir une ^^version.

Mehrdad Afshari
la source
25
-1 car la principale différence entre && et & n'est pas seulement le court-circuit. 1 && 2 est vrai, mais 1 & 2 est faux. Les courts-circuits ne sont qu'un effet secondaire pratique.
Brian Postow
6
La réponse ne parle pas &&et &du tout. Son argument est qu'il n'y a aucune raison de l'introduire ^^. La propriété qui ^^considérerait toute valeur non nulle comme 1n'étant pas vraiment utile, je pense. Ou du moins je ne vois aucune utilité.
Johannes Schaub - litb
6
Aussi C ++! = Autres-langages. En C et C ++, comme indiqué ci-dessus, le court-circuit n'est pas seulement agréable à avoir, mais il est fondamentalement important. :)
Johannes Schaub - litb
13
Ensuite, vous devriez lire la réponse de Dennis Ritchie expliquant pourquoi elle n'existe pas: it.usyd.edu.au/~dasymond/mirror/c-faq/misc/xor.dmr.html
Johannes Schaub - litb
5
Voici un lien de travail sur la réponse de Dennis Ritchie expliquant pourquoi elle n'existe pas: c-faq.com/misc/xor.dmr.html
Tobia
13

Un bon code publié a résolu le problème mieux que! A! =! B

Notez que j'ai dû ajouter le BOOL_DETAIL_OPEN / CLOSE pour que cela fonctionne sur 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
dés élégants
la source
6

Utilisez un simple:

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

Voici comment je pense que vous écrivez une comparaison 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
}

Preuve

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 preuve en est qu'une étude exhaustive des entrées et sorties montre que dans les deux tableaux, pour chaque ensemble d'entrées le résultat est toujours identique dans les deux tableaux.

Par conséquent, la question initiale était de savoir comment écrire:

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

La réponse serait

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

Ou si vous le souhaitez, écrivez

return !(A==5) == (B==5);
Indinfer
la source
! a! =! b semble être plus agréable car il convertit vos arguments en booléens pour vous.
xaxxon
3

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

La première partie est A OR B, qui est le OU inclusif; la deuxième partie est, PAS A ET B. Ensemble, vous obtenez A ou B, mais pas à la fois A et B.

Cela fournira le XOR prouvé dans la table de vérité ci-dessous.

|-----|-----|-----------|
|  A  |  B  |  A XOR B  |
|-----|-----|-----------|
|  T  |  T  |   False   |
|-----|-----|-----------|
|  T  |  F  |   True    |
|-----|-----|-----------|
|  F  |  T  |   True    |
|-----|-----|-----------|
|  F  |  F  |   False   |
|-----|-----|-----------|
Manger chez Joes
la source
Je ne creuse pas les performances de cette approche: quick-bench.com/PgNgGN8ATrKt7el1dAaJj7QtuF4
xaxxon
Il n'est pas nécessaire d'être défensif à ce sujet.
xaxxon
1
@xaxxon: Vous ne voyez pas l'intérêt de ces repères!? Dans StringCreation, vous avez utilisé dans la création de chaîne, mais dans le deuxième test, vous ne l'avez pas fait. Si vous placez du code identique dans les deux benchmarks et appelez des XOR différents pour chaque benchmark (XOR et XOR2), vous obtenez les mêmes résultats de benchmark. Alors qu'as-tu essayé de dire?
SoLaR
1

J'utilise "xor" (il semble que ce soit un mot-clé; au moins dans Code :: Blocks, il devient gras) tout comme vous pouvez utiliser "et" au lieu de &&et "ou" à la place de ||.

if (first xor second)...

Oui, c'est au niveau du bit. Désolé.

csiz
la source
2
Je suppose que ce sont des #defines cachés quelque part. Je suis à peu près sûr que "et" et "xor" ne sont pas des mots clés dans ansi C ... (du moins pas C79)
Brian Postow
1
@Brian Postow: Je ne sais pas ce qu'est C79, mais en C ++ 98 andet ce xorsont des macros de bibliothèque standard. Ils ne viennent pas de "quelque part", ils viennent de <iso646.h>. Ces macros sont également en C99 (pas sûr de C89 / 90).
AnT
5
@Brian Postow: ... xorreprésente cependant xor au niveau du bit , tandis que andest logique et.
AnT
1
J'ai mal saisi C89 comme C79 ... et et xor etc. ne sont pas dans ma copie de K&R. Je ne pense pas avoir jamais utilisé iso686.h, du moins pas sciemment ... et donc, oui, ce sont des #defines de quelque part, vous savez juste où ce quelque part est B-)
Brian Postow
2
Ils sont certainement en C99 utilisant cet en-tête. En C ++, ils sont intégrés dans le langage en tant que "jetons alternatifs", et vous pouvez struct A { compl A() { } };par exemple définir un destructeur.
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);
}

Cela fonctionne comme défini. Les conditions sont de détecter si vous utilisez Objective-C , qui demande BOOL au lieu de bool (la longueur est différente!)

Maxthon Chan
la source
3
Cela viole la règle du double soulignement.
Tamás Szelei
@ TamásSzelei Pas nécessairement car le compilateur ne voit pas cela car il est prétraité, et dans le monde Objective-C, les doubles soulignés sont assez courants.
Maxthon Chan
Bon point sur le préprocesseur, même si pour moi c'est toujours une odeur de code de cette façon (pourquoi utiliser une macro à la place un typedef quand même?). De plus, la question ne concernait pas Objective-C.
Tamás Szelei
@ TamásSzelei Eh bien, j'avais l'habitude de partager des fichiers d'en-tête dans plusieurs langues, et généralement tous les en-têtes proviennent d'Objective-C. Mon nouveau code ne sent plus trop maintenant, mais le double soulignement est toujours utilisé de temps en temps pour respecter les habitudes ObjC.
Maxthon Chan