Pourquoi les tableaux de références sont-ils illégaux?

149

Le code suivant ne compile pas.

int a = 1, b = 2, c = 3;
int& arr[] = {a,b,c,8};

Que dit la norme C ++ à ce sujet?

Je sais que je pourrais déclarer une classe qui contient une référence, puis créer un tableau de cette classe, comme indiqué ci-dessous. Mais je veux vraiment savoir pourquoi le code ci-dessus ne se compile pas.

struct cintref
{
    cintref(const int & ref) : ref(ref) {}
    operator const int &() { return ref; }
private:
    const int & ref;
    void operator=(const cintref &);
};

int main() 
{
  int a=1,b=2,c=3;
  //typedef const int &  cintref;
  cintref arr[] = {a,b,c,8};
}

Il est possible d'utiliser struct cintrefau lieu de const int &pour simuler un tableau de références.

Alexey Malistov
la source
1
Même si le tableau était valide, stocker une valeur brute «8» ne fonctionnerait pas. Si vous faisiez "intlink value = 8;", cela mourrait horriblement, car il est à peu près traduit par "const int & value = 8;". Une référence doit référencer une variable.
Grant Peters
3
intlink value = 8;fonctionne. vérifiez si vous ne croyez pas.
Alexey Malistov
7
Comme le souligne Alexey, il est parfaitement valide de lier une rvalue à une référence const.
avakar le
1
Ce qui ne marche pas , c'est ça operator=. Les références ne peuvent pas être réinstallées. Si vous voulez vraiment telle sémantique - même si je ne l' ai pas personnellement trouvé une situation dans laquelle ils sont utiles dans la pratique - alors std::reference_wrapperserait la façon de le faire, car il stocke en fait un pointeur , mais fournit comme référence operators et ne permet rasseyant. Mais alors j'utiliserais juste un pointeur!
underscore_d
1
L'opérateur = est privé et non implémenté, c'est-à-dire ce que dans C ++ 11 est = supprimer.
Jimmy Hartzell

Réponses:

148

En réponse à votre question sur la norme, je peux citer la norme C ++ §8.3.2 / 4 :

Il ne doit y avoir aucune référence à des références, aucun tableau de références et aucun pointeur vers des références.

Kirill V. Lyadvinsky
la source
32
Que dire de plus?
polyglotte du
9
il devrait y avoir des tableaux de références pour la même raison que nous avons des tableaux de pointeurs, les mêmes informations mais un traitement différent, non?
neu-rah
6
Si les développeurs de compilateurs décidaient, en tant qu'extension, d'autoriser les tableaux de références, serait-ce un succès? Ou y a-t-il de réels problèmes - peut-être du code ambigu - qui rendraient la définition de cette extension trop déroutante?
Aaron McDaid
23
@AaronMcDaid Je pense que la vraie raison - qui est si manifestement absente de cette discussion et d'autres similaires - est que si l'on avait un tableau de références, comment pourrait-on peut-être lever l'ambiguïté entre l'adresse d'un élément et l'adresse de son référent? L'objection immédiate à «Vous ne pouvez pas mettre une référence dans un tableau / conteneur / quoi que ce soit» est que vous pouvez mettre un structdont le seul membre est une référence. Mais alors, ce faisant, vous avez maintenant à la fois une référence et un objet parent à nommer ... ce qui signifie maintenant que vous pouvez indiquer sans ambiguïté quelle adresse vous voulez. Cela semble évident ... alors pourquoi personne ne l'a dit?
underscore_d
95
@polyglot What more is there to say?Une raison pour laquelle ? Je suis tout au sujet de la norme, mais le simple fait de le citer et de supposer que c'est la fin de la discussion semble être un moyen infaillible de détruire la pensée critique des utilisateurs - ainsi que tout potentiel d'évolution du langage, par exemple au-delà des limites d'omission ou restriction artificielle. Il y a trop de «parce que la norme dit« ici - et pas assez »et il est très raisonnable de dire cela, pour les raisons pratiques suivantes». Je ne sais pas pourquoi les votes positifs affluent si avidement aux réponses qui ne disent que la première sans même essayer d'explorer la seconde.
underscore_d
64

Les références ne sont pas des objets. Ils n'ont pas de stockage propre, ils font simplement référence aux objets existants. Pour cette raison, il n'a pas de sens d'avoir des tableaux de références.

Si vous voulez un objet léger qui référence un autre objet, vous pouvez utiliser un pointeur. Vous ne pourrez utiliser un structavec un membre de référence comme objets dans des tableaux que si vous fournissez une initialisation explicite pour tous les membres de référence pour toutes les structinstances. Les références ne peuvent pas être initialisées par défaut.

Edit: Comme le note jia3ep, dans la section standard sur les déclarations, il y a une interdiction explicite sur les tableaux de références.

CB Bailey
la source
6
Les références sont de la même nature que des pointeurs constants, et par conséquent elles prennent de la mémoire (stockage) pour pointer vers quelque chose.
inazaruk
7
Pas nécessairement. Le compilateur peut éviter de stocker l'adresse, s'il s'agit d'une référence à un objet local par exemple.
EFraim
4
Pas nécessairement. Les références dans les structures prennent généralement un peu de mémoire. Les références locales ne le font souvent pas. Dans tous les cas, au sens strict du terme, les références ne sont pas des objets et (c'était nouveau pour moi) les références nommées ne sont pas en fait des variables .
CB Bailey
26
Oui, mais c'est un détail d'implémentation. Un objet du modèle C ++ est une région de stockage typée. Une référence n'est explicitement pas un objet et il n'y a aucune garantie qu'elle occupe le stockage dans un contexte particulier.
CB Bailey
9
Dites-le de cette façon: vous pouvez avoir une structure de 16 refs à foo, et l'utiliser exactement de la même manière que je voudrais utiliser mon tableau de refs - sauf que vous ne pouvez pas indexer dans les 16 références foo . Ceci est une langue verrue IMHO.
greggo
28

C'est une discussion intéressante. Il est clair que les tableaux de références sont carrément illégaux, mais à mon humble avis, la raison pour laquelle ce n'est pas si simple que de dire «ils ne sont pas des objets» ou «ils n'ont pas de taille». Je soulignerais que les tableaux eux-mêmes ne sont pas des objets à part entière en C / C ++ - si vous vous y opposez, essayez d'instancier des classes de modèle stl en utilisant un tableau comme paramètre de modèle de `` classe '', et voyez ce qui se passe. Vous ne pouvez pas les retourner, les assigner, les passer en paramètres. (un paramètre de tableau est traité comme un pointeur). Mais il est légal de créer des tableaux de tableaux. Les références ont une taille que le compilateur peut et doit calculer - vous ne pouvez pas sizeof () une référence, mais vous pouvez créer une structure ne contenant que des références. Il aura une taille suffisante pour contenir tous les pointeurs qui implémentent les références. Vous pouvez'

struct mys {
 int & a;
 int & b;
 int & c;
};
...
int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs.a += 3  ;  // add 3 to ivar1

En fait, vous pouvez ajouter cette ligne à la définition de struct

struct mys {
 ...
 int & operator[]( int i ) { return i==0?a : i==1? b : c; }
};

... et maintenant j'ai quelque chose qui ressemble beaucoup à un tableau de références:

int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs[1] = my_refs[2]  ;  // copy arr[12] to ivar2
&my_refs[0];               // gives &my_refs.a == &ivar1

Maintenant, ce n'est pas un vrai tableau, c'est une surcharge d'opérateurs; il ne fera pas des choses que les tableaux font normalement comme sizeof (arr) / sizeof (arr [0]), par exemple. Mais il fait exactement ce que je veux qu'un tableau de références fasse, avec un C ++ parfaitement légal. Sauf (a) c'est difficile de configurer plus de 3 ou 4 éléments, et (b) il fait un calcul en utilisant un tas de?: Ce qui pourrait être fait en utilisant l'indexation (pas avec l'indexation normale de C-pointer-calcul-sémantique , mais indexation tout de même). J'aimerais voir un type de «tableau de référence» très limité qui peut réellement le faire. C'est-à-dire qu'un tableau de références ne serait pas traité comme un tableau général de choses qui sont des références, mais plutôt un nouveau 'tableau-de-références' faire avec des modèles).

cela fonctionnerait probablement, si cela ne vous dérange pas ce genre de méchant: refondre '* this' comme un tableau d'int * et renvoyer une référence faite à partir de l'un: (non recommandé, mais cela montre comment le bon 'tableau' travaillerait):

 int & operator[]( int i ) { return *(reinterpret_cast<int**>(this)[i]); }
Greggo
la source
1
Vous pouvez le faire en C ++ 11 std::tuple<int&,int&,int&> abcref = std::tie( a,b,c)- qui crée un «tableau» de références qui ne peuvent être indexées que par des constantes à la compilation en utilisant std :: get. Mais tu ne peux pas faire std::array<int&,3> abcrefarr = std::tie( a,b,c). Il y a peut-être un moyen de le faire que je ne connais pas. Il me semble qu'il devrait y avoir un moyen d'écrire une spécialisation std::array<T&,N>qui peut le faire - et donc une fonction std::tiearray(...)qui en renvoie une (ou qui permette std::array<T&,N>d'accepter un initialiseur contenant des adresses = {& v1, & v2 etc})
greggo
Votre proposition est idiote (IMO), et votre dernière ligne suppose qu'un int peut contenir un pointeur (généralement faux, du moins là où je travaille). Mais c'est la seule réponse ici avec une raison légitime expliquant pourquoi les tableaux de références sont interdits, donc +1
Nemo
@Nemo, ce n'était pas vraiment une proposition, mais juste pour illustrer que la fonctionnalité d'une telle chose n'est pas absurde à première vue. Je note dans mon dernier commentaire que c ++ a des choses qui se rapprochent. En outre, la dernière ligne à laquelle vous faites référence suppose que la mémoire implémentant une référence à un int peut être utilement aliasée en tant que pointeur vers int - la structure contient des références, pas des ints - et cela a tendance à être vrai. Je ne prétends pas qu'il soit portable (ni même à l'abri des règles de «comportement indéfini»). il était destiné à illustrer comment l'indexation pouvait être utilisée sous le capot pour éviter tout?:
greggo
C'est suffisant. Mais je pense que cette idée est "absurde à première vue", et c'est précisément pourquoi les tableaux de références sont interdits. Un tableau est avant tout un conteneur. Tous les conteneurs ont besoin d'un type d'itérateur pour que les algorithmes standard s'appliquent. Le type d'itérateur pour "tableau de T" est normalement "T *". Quel serait le type d'itérateur pour un tableau de références? La quantité de piratage linguistique qu'il faudrait pour résoudre tous ces problèmes est ridicule pour une fonctionnalité essentiellement inutile. En fait, peut-être que je vais faire de cette réponse ma propre réponse :-)
Nemo
@Nemo Il n'a pas besoin de faire partie du langage de base, donc le type d'itérateur étant «personnalisé» n'est pas un gros problème. Tous les différents types de conteneurs ont leurs types d'itérateurs. Dans ce cas, l'itérateur, déréférencé, ferait référence aux objets cibles, pas aux références dans le tableau, ce qui est parfaitement cohérent avec le comportement du tableau. Il peut encore nécessiter un nouveau comportement C ++ à prendre en charge en tant que modèle Le très simple std::array<T,N>, facilement défini dans le code de l'application, n'a pas été ajouté à std :: jusqu'à C ++ 11, probablement parce qu'il est verruqueux d'avoir cela sans moyen de = {1,2,3};Est - ce que c'était du «piratage de langage»?
greggo
28

Commentez votre modification:

La meilleure solution est std::reference_wrapper.

Détails: http://www.cplusplus.com/reference/functional/reference_wrapper/

Exemple:

#include <iostream>
#include <functional>
using namespace std;

int main() {
    int a=1,b=2,c=3,d=4;
    using intlink = std::reference_wrapper<int>;
    intlink arr[] = {a,b,c,d};
    return 0;
}
Youw
la source
Salut, cela a été déclaré en 09. Astuce recherchez les messages plus récents :)
Stígandr
9
Le message est ancien mais tout le monde ne connaît pas la solution avec reference_wrapper. Les gens ont besoin de lire le mouvement sur STL et STDlib de C ++ 11.
Youw
Cela donne un lien et un exemple de code, plutôt que de simplement mentionner le nom de la classe reference_wrapper.
David Stone
12

Un tableau est implicitement convertible en pointeur, et le pointeur vers référence est illégal en C ++

EFraim
la source
11
Il est vrai que vous ne pouvez pas avoir de pointeur vers une référence, mais ce n'est pas la raison pour laquelle vous ne pouvez pas avoir de tableau de références. Ce sont plutôt les deux symptômes du fait que les références ne sont pas des objets.
Richard Corden
1
Une structure ne peut contenir que des références, et elle aura une taille proportionnelle au nombre d'entre elles. Si vous aviez un tableau de références «arr», la conversion normale de tableau en pointeur n'aurait pas de sens, puisque «pointeur vers référence» n'a pas de sens. mais il serait logique d'utiliser simplement arr [i], de la même manière que st.ref quand 'st' est une structure contenant une ref. Hmm. Mais & arr [0] donnerait l'adresse du premier objet référencé, et & arr [1] - & arr [0] ne serait pas la même chose que & arr [2] - & arr [1] - cela créerait beaucoup d'étrangeté.
greggo
11

Étant donné int& arr[] = {a,b,c,8};, qu'est-ce que c'est sizeof(*arr)?

Partout ailleurs, une référence est traitée comme étant simplement la chose elle-même, elle sizeof(*arr)devrait donc l' être sizeof(int). Mais cela rendrait l'arithmétique du pointeur de tableau erronée sur ce tableau (en supposant que les références ne sont pas de la même largeur est ints). Pour éliminer l'ambiguïté, c'est interdit.

PaulMurrayCbr
la source
Oui. Vous ne pouvez pas avoir un tableau de quelque chose à moins qu'il n'ait une taille. Quelle est la taille d'une référence?
David Schwartz
4
@DavidSchwartz Faites un structdont le seul membre est cette référence et découvrez ..?
underscore_d
3
Cela devrait être la réponse acceptée, pas la stupide pédante qui jette simplement la norme à l'OP.
Tyson Jacobs
Il y a peut-être une faute de frappe. "en supposant que les références ne sont pas de la même largeur est ints" devrait être "en supposant que les références ne sont pas de la même largeur que les ints". Le changement est à as .
zhenguoli
Mais attendez, selon cette logique, vous ne pouviez pas avoir de variables de membre de référence.
Tyson Jacobs
10

Car comme beaucoup l'ont dit ici, les références ne sont pas des objets. ce sont simplement des alias. Il est vrai que certains compilateurs peuvent les implémenter en tant que pointeurs, mais la norme ne force / ne spécifie pas cela. Et comme les références ne sont pas des objets, vous ne pouvez pas les pointer. Stocker des éléments dans un tableau signifie qu'il existe une sorte d'adresse d'index (c'est-à-dire, pointant vers des éléments à un certain index); et c'est pourquoi vous ne pouvez pas avoir de tableaux de références, car vous ne pouvez pas les pointer.

Utilisez plutôt boost :: reference_wrapper ou boost :: tuple; ou juste des pointeurs.

navigateur
la source
5

Je pense que la réponse est très simple et qu'elle a à voir avec les règles sémantiques de références et la façon dont les tableaux sont gérés en C ++.

En bref: les références peuvent être considérées comme des structures qui n'ont pas de constructeur par défaut, donc toutes les mêmes règles s'appliquent.

1) Sémantiquement, les références n'ont pas de valeur par défaut. Les références ne peuvent être créées qu'en référençant quelque chose. Les références n'ont pas de valeur pour représenter l'absence de référence.

2) Lors de l'allocation d'un tableau de taille X, le programme crée une collection d'objets initialisés par défaut. Puisque la référence n'a pas de valeur par défaut, la création d'un tel tableau est sémantiquement illégale.

Cette règle s'applique également aux structures / classes qui n'ont pas de constructeur par défaut. L'exemple de code suivant ne compile pas:

struct Object
{
    Object(int value) { }
};

Object objects[1]; // Error: no appropriate default constructor available
Kristupas A.
la source
1
Vous pouvez créer un tableau de Object, il vous suffit de vous assurer de les initialiser tous: Object objects[1] = {Object(42)};. En attendant, vous ne pouvez pas créer un tableau de références, même si vous les initialisez toutes.
Anton3
4

Vous pouvez être assez proche avec cette structure de modèle. Cependant, vous devez initialiser avec des expressions qui sont des pointeurs vers T, plutôt que T; et ainsi, bien que vous puissiez facilement créer un 'fake_constref_array' de la même manière, vous ne pourrez pas le lier à rvalues ​​comme dans l'exemple de l'OP ('8');

#include <stdio.h>

template<class T, int N> 
struct fake_ref_array {
   T * ptrs[N];
  T & operator [] ( int i ){ return *ptrs[i]; }
};

int A,B,X[3];

void func( int j, int k)
{
  fake_ref_array<int,3> refarr = { &A, &B, &X[1] };
  refarr[j] = k;  // :-) 
   // You could probably make the following work using an overload of + that returns
   // a proxy that overloads *. Still not a real array though, so it would just be
   // stunt programming at that point.
   // *(refarr + j) = k  
}

int
main()
{
    func(1,7);  //B = 7
    func(2,8);     // X[1] = 8
    printf("A=%d B=%d X = {%d,%d,%d}\n", A,B,X[0],X[1],X[2]);
        return 0;
}

-> A = 0 B = 7 X = {0,8,0}

Greggo
la source
2

Un objet de référence n'a pas de taille. Si vous écrivez sizeof(referenceVariable), cela vous donnera la taille de l'objet référencé par referenceVariable, pas celle de la référence elle-même. Il n'a pas de taille propre, c'est pourquoi le compilateur ne peut pas calculer la taille requise par le tableau.

Carl Seleborg
la source
3
si tel est le cas, comment un compilateur peut-il calculer la taille d'une structure contenant uniquement une référence?
Juster le
Le compilateur connaît la taille physique d'une référence - c'est la même chose qu'un pointeur. Les références sont, en fait, des pointeurs sémantiquement glorifiés. La différence entre les pointeurs et les références est que les références ont pas mal de règles sémantiques placées dessus afin de réduire le nombre de bogues que vous pourriez créer par rapport aux pointeurs.
Kristupas A.
2

Lorsque vous stockez quelque chose dans un tableau, sa taille doit être connue (car l'indexation du tableau dépend de la taille). Selon le standard C ++ Il n'est pas spécifié si une référence nécessite ou non du stockage, par conséquent l'indexation d'un tableau de références ne serait pas possible.

Pradyot
la source
1
Mais en mettant ladite référence dans un struct, tout devient comme par magie spécifié, bien défini, garanti et de taille déterministe. Pourquoi, par conséquent, cette différence apparemment totalement artificielle existe-t-elle?
underscore_d
... bien sûr, si l'on commence réellement à réfléchir à ce que structconfère l'emballage , de bonnes réponses possibles commencent à se suggérer - mais pour moi, personne ne l'a encore fait, bien que ce soit l'un des défis immédiats à tous les justifie pourquoi vous ne pouvez pas utiliser des références non emballées.
underscore_d
2

Juste pour ajouter à toute la conversation. Étant donné que les tableaux nécessitent des emplacements mémoire consécutifs pour stocker l'élément, donc si nous créons un tableau de références, il n'est pas garanti qu'elles seront à un emplacement mémoire consécutif, l'accès sera donc un problème et nous ne pouvons même pas appliquer toutes les opérations mathématiques sur tableau.

Amit Kumar
la source
Considérez le code ci-dessous de la question originale: int a = 1, b = 2, c = 3; int & arr [] = {a, b, c, 8}; Il y a très peu de chances que a, b, c résident à un emplacement mémoire consécutif.
Amit Kumar
C'est loin d'être le problème principal du concept de conservation des références dans un conteneur.
underscore_d
0

Considérez un tableau de pointeurs. Un pointeur est vraiment une adresse; Ainsi, lorsque vous initialisez le tableau, vous dites de manière analogue à l'ordinateur, "allouez ce bloc de mémoire pour contenir ces numéros X (qui sont les adresses d'autres éléments)". Ensuite, si vous changez l'un des pointeurs, vous changez simplement ce qu'il pointe; c'est toujours une adresse numérique qui se trouve elle-même au même endroit.

Une référence est analogue à un alias. Si vous deviez déclarer un tableau de références, vous diriez essentiellement à l'ordinateur, "allouez cette goutte amorphe de mémoire constituée de tous ces différents éléments éparpillés."

Dan Tao
la source
1
Pourquoi, alors, faire un structdont le seul membre est une référence aboutit-il à quelque chose de complètement légal, prévisible et non amorphe? Pourquoi un utilisateur doit-il envelopper une référence pour qu'il obtienne comme par magie des pouvoirs capables de faire des tableaux, ou s'agit-il simplement d'une restriction artificielle? Aucune réponse de l'OMI n'a abordé ce problème directement. Je suppose que la réfutation est `` L'objet d'emballage garantit que vous pouvez utiliser la sémantique de la valeur, sous la forme de l'objet d'emballage, de sorte qu'il garantit que vous pouvez avoir les garanties de valeurs, qui sont nécessaires parce que, par exemple, comment lever l'ambiguïté entre l'élément et l'adresse de l'arbitre '' ... C'est ce que tu voulais dire?
underscore_d
-2

En fait, il s'agit d'un mélange de syntaxe C et C ++.

Vous devez soit utiliser des tableaux C purs, qui ne peuvent pas être des références, car les références font partie de C ++ uniquement. Ou vous utilisez la méthode C ++ et utilisez la classe std::vectorou std::arraypour vos besoins.

Quant à la partie éditée: même si le structest un élément de C, vous définissez un constructeur et des fonctions d'opérateur, ce qui en fait un C ++ class. Par conséquent, vous structne compileriez pas en C pur!

Thargon
la source
À quoi veux-tu en venir? std::vectorou std::arrayne peut pas contenir de références non plus. La norme C ++ est assez explicite qu'aucun conteneur ne peut.
underscore_d