web-dev-qa-db-fra.com

Optimisation de la loi de De Morgan avec des opérateurs surchargés

Chaque programmeur doit savoir que:

De Morgan 1
De Morgan 2
( Lois de De Morgan)

Dans certaines circonstances, afin d'optimiser le programme, il peut arriver que le compilateur modifie (!p && !q) à (!(p || q)).

Les deux expressions sont équivalentes et cela ne fait aucune différence d'évaluer la première ou la seconde.
Mais en C++, il est possible de surcharger les opérateurs, et l'opérateur surchargé peut ne pas toujours respecter cette propriété. Donc, transformer le code de cette façon modifiera réellement le code.

Le compilateur doit-il utiliser les lois de De Morgan lorsque !, || et && sont surchargés?

67
Alex

Notez que:

Opérateurs intégrés && et || effectuer une évaluation de court-circuit (ne pas évaluer le deuxième opérande si le résultat est connu après avoir évalué le premier), mais les opérateurs surchargés se comportent comme des appels de fonction réguliers et évaluent toujours les deux opérandes .

... Parce que les propriétés de court-circuit de l'opérateur && et de l'opérateur || ne s'appliquent pas aux surcharges, et comme les types avec sémantique booléenne sont rares, seules deux classes de bibliothèque standard surchargent ces opérateurs ...

Source: http://en.cppreference.com/w/cpp/language/operator_logical (c'est moi qui souligne)

Et cela:

S'il existe un candidat écrit par l'utilisateur avec le même nom et les mêmes types de paramètres qu'une fonction opérateur candidate intégrée, la fonction opérateur intégrée est masquée et n'est pas inclus dans l'ensemble des fonctions candidates.

Source: n4431 13.6 Opérateurs intégrés [over.built] (c'est moi qui souligne)

Pour résumer: les opérateurs surchargés se comportent comme des fonctions normales écrites par l'utilisateur.

NON, le compilateur ne remplacera pas un appel d'une fonction écrite par l'utilisateur par un appel d'une autre fonction écrite par l'utilisateur. Sinon, cela violerait potentiellement la règle "comme si" .

77
sergej

Je pense que vous avez répondu à votre propre question: non, un compilateur ne peut pas faire ça. Non seulement les opérateurs peuvent être surchargés, certains ne peuvent même pas être définis. Par exemple, vous pouvez avoir operator && et operator ! défini et operator || pas du tout défini.

Notez qu'il existe de nombreuses autres lois que le compilateur ne peut pas suivre. Par exemple, il ne peut pas changer p||q à q||p, aussi bien que x+y à y+x.

(Tout ce qui précède s'applique aux opérateurs surchargés, car c'est ce que la question demande.)

17
Petr

Non, dans ce cas, la transformation serait invalide. L'autorisation de transformer !p && !q En !(p || q) est implicite, par la règle comme si. La règle "comme si" permet toute transformation qui, grosso modo, ne peut pas être observée par un programme correct. Lorsque des opérateurs surchargés sont utilisés et détectent la transformation, cela signifie automatiquement que la transformation n'est plus autorisée.

9
user743382

Les opérateurs surchargés en soi ne sont que du sucre syntaxique pour les appels de fonction; le compilateur lui-même n'est pas autorisé à faire d'hypothèse sur les propriétés qui peuvent ou non contenir de tels appels. Les optimisations qui exploitent les propriétés de certains opérateurs spécifiques (par exemple, De Morgan pour les opérateurs booléens, la commutativité pour les sommes, la distributivité pour la somme/produit, la transformation de la division intégrale dans une multiplication appropriée, ...) ne peuvent être utilisées que lorsque les "vrais opérateurs" sont utilisés.

Notez plutôt que certaines parties de la bibliothèque standard peuvent associer une signification sémantique spécifique aux opérateurs surchargés - par exemple, std::sort par défaut attend un operator< qui respecte un ordre faible strict entre les éléments - mais cela est bien sûr répertorié dans les prérequis de chaque algorithme/conteneur.

(accessoirement, surcharge && et || devrait être évité de toute façon car ils perdent leurs propriétés de court-circuit lorsqu'ils sont surchargés, de sorte que leur comportement devient surprenant et donc potentiellement dangereux)

5
Matteo Italia

Vous demandez si le compilateur peut réécrire arbitrairement votre programme pour faire quelque chose que vous ne l'avez pas écrit.

La réponse est: bien sûr que non!

  • Lorsque les lois de De Morgan s'appliquent, elles peuvent être appliquées.
  • Là où ils ne le font pas, ils ne le peuvent pas.

C'est vraiment aussi simple que cela.

Pas directement.

Si p et q sont des expressions de sorte que p n'a pas d'opérateurs surchargés, l'évaluation de court-circuit est en vigueur: l'expression q ne sera évaluée que si p est faux.

Si p est de type non primitif, il n'y a pas d'évaluation de court-circuit et la fonction surchargée pourrait être n'importe quoi - même sans rapport avec l'utilisation conventionnelle.

Le compilateur fera ses optimisations à sa manière. Peut-être que cela pourrait entraîner des identités de Morgan, mais pas au niveau du remplacement de la condition if.

3
renonsz

Les lois de DeMorgan s'appliquent à la sémantique de ces opérateurs. La surcharge s'applique à la syntaxe de ces opérateurs. Il n'y a aucune garantie qu'un opérateur surchargé implémente la sémantique nécessaire à l'application des lois de DeMorgan.

3
Pete Becker

Mais en C++, il est possible de surcharger les opérateurs, et l'opérateur surchargé peut ne pas toujours respecter cette propriété.

L'opérateur surchargé n'est plus un opérateur, c'est un appel de fonction.

class Boolean
{
  bool value;

  ..

  Boolean operator||(const Boolean& b)
  {
      Boolean c;
      c.value = this->value || b.value;
      return c;
  }

  Boolean logical_or(const Boolean& b)
  {
      Boolean c;
      c.value = this->value || b.value;
      return c;
  }
}

Donc, cette ligne de code

Boolean a (true);
Boolean b (false);

Boolean c = a || b;

est équivalent à cela

Boolean c = a.logical_or(b);
1
Khaled.K