web-dev-qa-db-fra.com

Pourquoi ne pouvons-nous pas déclarer une variable de type void?

Je cherche une explication formelle de ce fait dans la norme. J'ai trouvé ce que dit 3.9.1/9 et en essayant de donner une explication, j'ai utilisé cette section.

Section 3.9.1/9, N3797:

Le type void a un ensemble de valeurs vide. Le type void est un type incomplet qui ne peut pas être complété. Il est utilisé comme type de retour pour les fonctions qui ne renvoient pas de valeur. Toute expression peut être explicitement convertie en type cv void (5.4). Une expression de type void ne doit être utilisée que comme instruction d'expression (6.2), comme opérande d'une expression virgule (5.18), comme deuxième ou troisième opérande de?: (5.16), comme opérande de typeid, noexcept, ou decltype, comme expression dans une instruction return (6.6.3) pour une fonction avec le type de retour void, ou comme l'opérande d'une conversion explicite en type cv void.

Je ne comprends pas comment cela implique du fait que le type void a un ensemble vide de valeurs?

Supposons que le type T ait un ensemble de valeurs vide. Pourquoi le compilateur envoie-t-il une erreur lorsqu'il rencontre la ligne suivante:

extern T v; 

On peut décalcrer une variable de type incomplet de la manière suivante:

#include <iostream>
#include <cstring>

using namespace std;

struct Foo;

extern Foo f; //OK!

int main()
{
}

et ça marche bien

[~ # ~] démo [~ # ~]

Cela ne peut pas être fait sur un type vide

#include <iostream>
#include <cstring>

using namespace std;

extern void f; //compile-time error

int main()
{
}

[~ # ~] démo [~ # ~]

38
user2953119

Vous ne pouvez pas déclarer une variable de type void car les variables doivent avoir un type d'objet ou être des références, extern void f; ne déclare pas de référence et void n'est pas un type d'objet:

Section 3 [basic] dit ça

Une variable est introduite par la déclaration d'une référence autre qu'un membre de données non statique ou d'un objet.

Section 3.9 [basic.types] dit ça

Un type d'objet est un type (éventuellement qualifié par cv) qui n'est ni un type de fonction, ni un type de référence, ni un type void.

24
Ben Voigt

"le type void est un type incomplet"

Vous ne pouvez pas créer de variables de type incomplet

"... qui ne peut pas être complété"

Alors que votre exemple de structure incomplète extern peut être complété ultérieurement, le compilateur sait que toute déclaration de type void ne peut jamais être complétée.

9
IronMensan

[modifier] La réponse ci-dessous fait des observations valables, mais elles sont contradictoires. Comme ceux-ci peuvent être utiles, je ne les supprimerai pas, mais consultez la réponse de Ben Voight et les commentaires pour une approche plus simple.

Vos observations sur les déclarations extern sont spécifiquement autorisées par 7.1.1/8:

Le nom d'une classe déclarée mais non définie peut être utilisé dans une déclaration externe. Une telle déclaration ne peut être utilisée que de manière à ne pas nécessiter de type de classe complet.

void n'est pas une "classe déclarée mais non définie", et il n'y a pas d'autre exception dans 7.1.1 qui s'applique.

De plus, 3.9/5 est assez explicite qu'il est en fait autorisé:

Une classe qui a été déclarée mais non définie, un type d'énumération dans certains contextes (7.2), ou un tableau de taille inconnue ou de type d'élément incomplet, est un type d'objet incomplètement défini. [45] Les types d'objets incomplètement définis et les types void sont des types incomplets (3.9.1). Les objets ne doivent pas être définis pour avoir un type incomplet.

Souligner le mien . Cette partie de la norme est assez spécifique sur les différences entre les définitions et les déclarations, donc par omission elle spécifie que les déclarations sont autorisées.

3
MSalters

void est un type incomplet - vous pouvez niquement leur déclarer des pointeurs et les utiliser dans les signatures de fonction. Évidemment, extern Foo f;est autorisé car struct Foopeut être défini dans une autre unité de compilation (et si c'est pas l'erreur sera détectée par l'éditeur de liens), mais void ne peut jamais être "defined" (et le compilateur le sait, bien sûr) donc void est assez spécial dans ce cas.

2
Paul Evans

Si la variable a un ensemble de valeurs vide, elle ne peut être utilisée pour rien.

Vous ne pouvez pas lui attribuer, car il n'y a aucune valeur possible à attribuer.

Vous ne pouvez pas y accéder, car vous ne l'avez jamais affecté, il a donc une valeur indéterminée.

Puisqu'il n'y a pas de valeurs possibles, il n'y a pas de taille de la variable.

void est juste utilisé comme espace réservé dans des emplacements variables. Il est utilisé comme type de retour pour indiquer que la fonction ne renvoie pas de valeur. Il est utilisé dans C dans la liste des arguments pour indiquer que la fonction ne prend aucun argument (pour résoudre une ambiguïté de la version pré-prototype du langage). Et il est utilisé avec les déclarations de pointeurs pour créer des pointeurs génériques qui peuvent être traduits en tout autre type de pointeur. Il n'y a pas une telle utilisation analogue dans les déclarations de variables.

2
Barmar

Comme C et C++ supposent que tous les objets peuvent être comparés pour leur identité en comparant leurs adresses, ils doivent s'assurer que tous les objets ont une taille non nulle fixe. Sans cette exigence, il y a en fait de nombreux cas où il serait quelque peu utile de déclarer des objets de taille nulle [par ex. dans du code qui utilise des modèles qui contiennent des champs qui seront parfois utiles et parfois non, ou comme un moyen de forcer une structure à être complétée à un certain alignement nécessitant qu'elle contienne un élément nécessitant un tel alignement]. En l'état, cependant, les types de taille nulle seraient incompatibles avec le fait que la règle spécifiant que chaque objet a une adresse unique n'inclut aucune exception qui permettrait l'existence d'objets de taille nulle pouvant partager une adresse.

Même si des objets de taille nulle étaient autorisés, un "pointeur vers un objet inconnu" ne devrait pas être identique à un "pointeur vers un objet de taille nulle". Étant donné que le type void* est utilisé pour le premier, cela impliquerait que quelque chose d'autre devrait être utilisé pour le second, ce qui impliquerait à son tour que quelque chose d'autre que void devrait être le type de chose vers laquelle un objet de taille zéro pointe .

2
supercat

Eh bien - je ne vois vraiment pas la raison derrière cela. Ça va être génial si de cette façon nous pouvons déclarer une variable de type inconnu. Quelque chose comme "void *" et des tableaux de taille inconnue. Imaginez un code comme celui-ci:

#include <iostream>
#include <cstring>

using namespace std;

extern void f;

int main()
{
    cout << (int &)f << endl; //cout 'f' as it was integer
}

struct {
    int a;
    double b;
} f{};

Vous pouvez maintenant faire quelque chose de similaire avec les tableaux:

#include <iostream>
#include <cstring>

using namespace std;

struct Foo;

extern int arr[];

int main()
{
    cout << arr[2] << endl;
}

int arr[4]{};

Vie exemple .

0
AnArrayOfFunctions