Pourquoi les fonctions supprimées en C ++ 11 participent-elles à la résolution des surcharges?

Réponses:

114

La moitié du but de la = deletesyntaxe est de pouvoir empêcher les gens d'appeler certaines fonctions avec certains paramètres. C'est principalement pour empêcher les conversions implicites dans certains scénarios spécifiques. Afin d'interdire une surcharge particulière, il doit participer à la résolution de la surcharge.

La réponse que vous citez vous donne un exemple parfait:

struct onlydouble {
  onlydouble(std::intmax_t) = delete;
  onlydouble(double);
};

Si deletela fonction était entièrement supprimée, cela rendrait la = deletesyntaxe équivalente à ceci:

struct onlydouble2 {
  onlydouble2(double);
};

Vous pouvez faire ceci:

onlydouble2 val(20);

C'est du C ++ légal. Le compilateur examinera tous les constructeurs; aucun d'entre eux ne prend directement un type entier. Mais l'un d'eux peut le prendre après une conversion implicite. Alors ça va appeler ça.

onlydouble val(20);

Ce n'est pas du C ++ légal. Le compilateur examinera tous les constructeurs, y compris les deleted. Il verra une correspondance exacte, via std::intmax_t(qui correspondra exactement à n'importe quel littéral entier). Ainsi, le compilateur le sélectionnera et émettra immédiatement une erreur, car il a sélectionné une deletefonction d.

= deletesignifie «je l'interdis», pas simplement «cela n'existe pas». C'est une déclaration beaucoup plus forte.

Je demandais pourquoi la norme C ++ dit = supprimer signifie "je l'interdis" au lieu de "cela n'existe pas"

C'est parce que nous n'avons pas besoin d'une grammaire spéciale pour dire «cela n'existe pas». Nous obtenons cela implicitement en ne déclarant tout simplement pas le «ceci» en question. «J'interdis cela» représente une construction qui ne peut être réalisée sans une grammaire spéciale. Donc, nous obtenons une grammaire spéciale pour dire "j'interdis cela" et pas l'autre chose.

La seule fonctionnalité que vous gagneriez en ayant une grammaire explicite «cela n'existe pas» serait d'empêcher quelqu'un de déclarer plus tard qu'elle existe. Et ce n'est tout simplement pas assez utile pour avoir besoin de sa propre grammaire.

il n'y a autrement aucun moyen de déclarer que le constructeur de copie n'existe pas et son existence peut provoquer des ambiguïtés absurdes.

Le constructeur de copie est une fonction membre spéciale. Chaque classe a toujours un constructeur de copie. Tout comme ils ont toujours un opérateur d'affectation de copie, un constructeur de déplacement, etc.

Ces fonctions existent; la question est seulement de savoir s'il est légal de les appeler. Si vous essayiez de dire que cela = deletesignifiait qu'elles n'existaient pas, alors la spécification devrait expliquer ce que cela signifie pour une fonction de ne pas exister. Ce n'est pas un concept traité par la spécification.

Si vous essayez d'appeler une fonction qui n'a pas encore été déclarée / définie, le compilateur fera une erreur. Mais cela provoquera une erreur à cause d'un identifiant non défini , pas à cause d'une erreur "la fonction n'existe pas" (même si votre compilateur le signale de cette façon). Différents constructeurs sont tous appelés par résolution de surcharge, leur "existence" est donc gérée à cet égard.

Dans tous les cas, il y a soit une fonction déclarée via un identifiant, soit un constructeur / destructeur (également déclaré via un identifiant, juste un identifiant de type). La surcharge de l'opérateur cache l'identifiant derrière le sucre syntaxique, mais il est toujours là.

La spécification C ++ ne peut pas gérer le concept de «fonction qui n'existe pas». Il peut gérer une incompatibilité de surcharge. Il peut gérer une ambiguïté de surcharge. Mais il ne sait pas ce qui n'est pas là. Ainsi = deleteest défini en termes de "tentatives pour appeler cet échec" bien plus utiles que de "prétendre que je n'ai jamais écrit cette ligne", moins utile.

Et encore une fois, relisez la première partie. Vous ne pouvez pas faire cela avec «la fonction n'existe pas». C'est une autre raison pour laquelle il est défini de cette façon: parce que l'un des principaux cas d'utilisation de la = deletesyntaxe est de pouvoir forcer l'utilisateur à utiliser certains types de paramètres, à effectuer un cast explicite, etc. Fondamentalement, pour déjouer les conversions de type implicites.

Votre suggestion ne ferait pas cela.

Nicol Bolas
la source
1
@Mehrdad: Si vous avez besoin de plus de précisions sur la raison pour laquelle = delete ne fonctionne pas comme vous le souhaitez, vous devez être plus clair sur la sémantique exacte que vous pensez que = delete devrait avoir. Doit-il être "faire comme si je n'avais jamais écrit cette ligne?" Ou devrait-il être autre chose?
Nicol Bolas
1
Non, je veux dire que je m'attendais = deleteà vouloir dire "ce membre n'existe pas", ce qui impliquerait qu'il ne pourrait pas participer à la résolution des surcharges.
user541686
6
@Mehrdad: Et cela me ramène à mon point d'origine, c'est pourquoi je l'ai posté: si cela = deletevoulait dire "ce membre n'existe pas", alors le premier exemple que j'ai posté ne pourrait pas empêcher les gens de passer des entiers au onlydoubleconstructeur de. , car la onlydoublesurcharge supprimée n'existerait pas . Cela ne participerait pas à la résolution des surcharges et ne vous empêcherait donc pas de passer des entiers. Ce qui est la moitié du point de la = deletesyntaxe: pouvoir dire: "Vous ne pouvez pas passer X implicitement à cette fonction."
Nicol Bolas
3
@Mehrdad: Par cette logique, pourquoi en avez-vous besoin =delete? Après tout, nous pouvons dire "non copiable" en faisant exactement la même chose: déclarer le constructeur de copie / l'affectation privée. Notez également que déclarer quelque chose de privé ne le rend pas impossible; le code dans la classe peut toujours l'appeler. Donc ce n'est pas la même chose que = delete. Non, la = deletesyntaxe nous permet de faire quelque chose qui était auparavant très gênant et impénétrable auparavant d'une manière beaucoup plus évidente et raisonnable.
Nicol Bolas
2
@Mehrdad: Parce que ce dernier est possible : ça s'appelle "ne pas le déclarer". Alors ça n'existera pas. Quant à savoir pourquoi nous avons besoin d'une syntaxe pour cacher une surcharge plutôt que d'abuser du privé ... vous demandez-vous vraiment pourquoi nous devrions avoir un moyen d'énoncer explicitement quelque chose, plutôt que d'abuser d'une autre fonctionnalité pour obtenir le même effet ? Ce qui se passe est simplement plus évident pour quiconque lit le code. Cela rend le code plus facilement compréhensible pour l'utilisateur et le rend plus facile à écrire, tout en résolvant les problèmes dans la solution de contournement. Nous n'avons pas non plus besoin de lambdas.
Nicol Bolas
10

Le brouillon de travail C ++ 2012-11-02 ne fournit pas de justification derrière cette règle, juste quelques exemples

8.4.3 Définitions supprimées [dcl.fct.def.delete]
...
3 [ Exemple : On peut imposer une initialisation non par défaut et une initialisation non intégrale avec

struct onlydouble {  
  onlydouble() = delete; // OK, but redundant  
  onlydouble(std::intmax_t) = delete;  
  onlydouble(double);  
};  

- end example ]
[ Exemple : On peut empêcher l'utilisation d'une classe dans certaines nouvelles expressions en utilisant des définitions supprimées d'un opérateur déclaré par l'utilisateur nouveau pour cette classe.

struct sometype {  
  void *operator new(std::size_t) = delete;  
  void *operator new[](std::size_t) = delete;  
};  
sometype *p = new sometype; // error, deleted class operator new  
sometype *q = new sometype[3]; // error, deleted class operator new[]  

- exemple de fin ]
[ Exemple : On peut rendre une classe non copiable, c'est-à-dire déplacer uniquement, en utilisant les définitions supprimées du constructeur de copie et de l'opérateur d'affectation de copie, puis en fournissant les définitions par défaut du constructeur de déplacement et de l'opérateur d'affectation de déplacement.

struct moveonly {  
  moveonly() = default;  
  moveonly(const moveonly&) = delete;  
  moveonly(moveonly&&) = default;  
  moveonly& operator=(const moveonly&) = delete;  
  moveonly& operator=(moveonly&&) = default;  
  ~moveonly() = default;  
};  
moveonly *p;  
moveonly q(*p); // error, deleted copy constructor  

- fin d'exemple ]

Olaf Dietsche
la source
4
La justification semble très claire d'après les exemples, n'est-ce pas?
Jesse Good