web-dev-qa-db-fra.com

Combien de code C++ existant serait cassé si void était défini comme `struct void {};`

void est une verrue bizarre dans le système de types C++. C'est un type incomplet qui ne peut pas être complété, et il a toutes sortes de règles magiques sur les manières limitées dont il peut être utilisé:

Un type cvvoid est un type incomplet qui ne peut pas être complété; un tel type a un ensemble vide de valeurs. Il est utilisé comme type de retour pour les fonctions qui ne renvoient pas de valeur . Toute expression peut être explicitement convertie au type cvvoid ([expr.cast]) . Une expression de type cvvoid ne doit être utilisée que comme une déclaration d'expression, en tant qu'opérande d'une expression de virgule, en tant que deuxième ou troisième opérande de ?: ([expr.cond]), en tant qu'opérande de typeid, noexcept , ou decltype, en tant qu'expression dans une instruction return pour une fonction avec le type de retour cvvoid, ou en tant qu'opérande d'une conversion explicite en type cvvoid.

(N4778, [basic.fundamental] ¶9 )

En plus de la sensation de démangeaisons suscitée par toutes ces règles étranges, en raison des possibilités limitées d'utilisation, il apparaît souvent comme un cas particulier douloureux lors de la rédaction de modèles; le plus souvent, on a l'impression que nous aimerions qu'il se comporte davantage comme std::monostate .


Imaginons un instant qu'au lieu de la citation ci-dessus, la norme dise à propos de void quelque chose comme

C'est un type avec une définition équivalente à:

struct void {
    void()=default;
    template<typename T> explicit void(T &&) {}; // to allow cast to void
};

tout en gardant la magie void * - peut aliaser n'importe quel objet, les pointeurs de données doivent survivre à l'aller-retour jusqu'à void *.

Ce:

  • devrait couvrir les cas d'utilisation existants du type void "appropriés";
  • pourrait probablement permettre l’élimination d’une quantité décente de bric-à-brac répandue dans la norme - par exemple. [expr.cond] ¶2 serait probablement inutile, et [stmt.return] serait grandement simplifié (tout en conservant "l'exception" qui return sans expression est autorisée pour void et que " s'écoulant "d'une fonction void équivaut à return;);
  • devrait toujours être tout aussi efficace - l'optimisation des classes vides est désormais prise en charge partout;
  • être intrinsèquement compatible sur les ABI modernes, et pourrait toujours être utilisé par le compilateur pour les plus anciens.

En plus d'être compatibles, cela fournirait:

  • la construction, la copie et le déplacement de ces objets vides, en éliminant les cas particuliers généralement nécessaires dans les modèles;
  • bonus arithmétique de pointeur sur void *, fonctionnant comme pour char *, qui est une extension courante, très utile pour manipuler des tampons binaires.

Maintenant, à part les valeurs de retour éventuellement modifiées de la substance <type_traits>, qu'est-ce que cela pourrait éventuellement briser dans un code bien formé selon les règles actuelles (C++ 17)?

19
Matteo Italia

Il y a une proposition pour cela, c'est p0146: Régulier Void

Ci-dessous est présentée une définition de structure analogue à ce qui est proposé comme nul dans ce document. La définition actuelle n'est pas une classe tapez, mais cela sert comme une approximation assez précise de ce qui est proposé et comment les développeurs peuvent penser à vide. Quel devrait être remarqué, c’est que cela peut être considéré comme une fonctionnalité ajoutée au fichier type de vide existant, un peu comme ajouter une fonction membre spéciale à un autre type existant qui ne l’avait pas auparavant, comme l’ajout d’un déplacement constructeur à un type précédemment non copiable. Cette comparaison n’est pas entièrement analogue parce que vide n’est pour le moment pas un type ordinaire, mais c’est est une description raisonnable et informelle, les détails étant abordés plus tard.

struct void {
  void() = default;
  void(const void&) = default;
  void& operator =(const void&) = default;

  template <class T>
  explicit constexpr void(T&&) noexcept {}
};

constexpr bool operator ==(void, void) noexcept { return true; }
constexpr bool operator !=(void, void) noexcept { return false; }
constexpr bool operator <(void, void) noexcept { return false; }
constexpr bool operator <=(void, void) noexcept { return true; }
constexpr bool operator >=(void, void) noexcept { return true; }
constexpr bool operator >(void, void) noexcept { return false; }

Il a été bien reçu à Oulu, réunion de juin 2016. Compte-rendu du voyage :

Void regular, une proposition visant à supprimer la plupart des cas de traitement des cas spéciaux dans la langue, le faisant se comporter comme tout autre type. L'idée générale a bénéficié d'un soutien croissant depuis sa présentation initiale, il y a deux réunions, mais certains détails étaient toujours controversés, notamment la possibilité de supprimer des pointeurs de type void *. L'auteur a été encouragé à revenir avec une proposition révisée et peut-être une mise en œuvre permettant d'éviter les complications inattendues.

J'ai discuté avec l'auteur et il a confirmé qu'il attend essentiellement une mise en œuvre, une fois que celle-ci est mise en œuvre, il prévoit de ramener la proposition.

Le document discute longuement de ce qui a changé et de la raison pour laquelle il n'est pas vraiment citable, mais les questions FAQ sont:

  • Cette proposition n'introduit-elle pas plus de casiers spéciaux?
  • Pourquoi sizeof (void) n'est-il pas égal à 0?
  • Est-ce que cela std :: enable_if?
  • En pratique, cela briserait-il la compatibilité ABI?
  • Constexpr_if ne facilite-t-il pas la création de branches pour void?
  • N'est-il pas illogique de soutenir une opération pour vide?
  • Cela ne supprime-t-il pas la notion de "pas de résultat?"
  • N'est-ce pas un changement dans la signification du vide?
14
Shafik Yaghmour
  • void est un type avec un domaine empty (il n'a pas de valeurs possibles);
  • struct foo { } est un type avec un domaine non-vide (il existe une valeur de ce type).

En d'autres termes, void est un type bottom alors qu'un struct void {} potentiel serait un type unité .

Remplacer le premier par le second détruit essentiellement le monde entier. Ce n'est pas tout à fait différent de décider que 0 est égal à 1 en C++.

0
einpoklum