¿Por qué son ilegales las matrices de referencias?

149

El siguiente código no se compila.

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

¿Qué dice el estándar C ++ sobre esto?

Sé que podría declarar una clase que contiene una referencia, luego crear una matriz de esa clase, como se muestra a continuación. Pero realmente quiero saber por qué el código anterior no se compila.

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};
}

Es posible usar en struct cintreflugar de const int &simular una matriz de referencias.

Alexey Malistov
fuente
1
Incluso si la matriz fuera válida, almacenar un valor '8' sin procesar no funcionaría. Si hicieras "intlink value = 8;", moriría horriblemente, porque prácticamente se traduce a "const int & value = 8;". Una referencia debe hacer referencia a una variable.
Grant Peters el
3
intlink value = 8;funciona. comprueba si no crees.
Alexey Malistov
77
Como señala Alexey, es perfectamente válido vincular un valor a una referencia constante.
avakar
1
Lo que no funciona es eso operator=. Las referencias no pueden ser reubicadas. Si realmente quiere este tipo de semántica - aunque no he encontrado personalmente una situación en la que son útiles en la práctica - a continuación, std::reference_wrappersería la manera de hacerlo, ya que realmente almacena un puntero, pero hay referencias similares a operators y no permite volver a colocar. ¡Pero entonces solo usaría un puntero!
underscore_d
1
El operador = es privado y no está implementado, es decir, lo que en C ++ 11 es = eliminar.
Jimmy Hartzell

Respuestas:

148

Respondiendo a su pregunta sobre el estándar, puedo citar el Estándar C ++ §8.3.2 / 4 :

No debe haber referencias a referencias, ni matrices de referencias , ni punteros a referencias.

Kirill V. Lyadvinsky
fuente
32
¿Qué más hay que decir?
polyglot
9
debería haber matrices de referencias por la misma razón por la que tenemos matrices de punteros, misma información pero diferente manejo, ¿no?
neu-rah
66
Si los desarrolladores del compilador decidieran, como extensión, permitir conjuntos de referencias, ¿sería un éxito? ¿O hay algunos problemas reales, tal vez código ambiguo, que podrían hacer que sea demasiado confuso definir esta extensión?
Aaron McDaid el
23
@AaronMcDaid Creo que la verdadera razón, que es tan notoriamente ausente de esta y otras discusiones similares, es que si uno tuviera una serie de referencias, ¿cómo podría desambiguarse entre la dirección de un elemento y la dirección de su referente? La objeción inmediata a "No puede poner una referencia en una matriz / contenedor / lo que sea" es que puede poner un structmiembro cuyo único miembro sea una referencia. Pero luego, al hacerlo, ahora tiene una referencia y un objeto principal para nombrar ... lo que ahora significa que puede indicar inequívocamente qué dirección desea. Parece obvio ... entonces, ¿por qué nadie lo ha dicho?
underscore_d
95
@polyglot ¿ What more is there to say?Una razón de por qué ? Me interesa el Estándar, pero solo citarlo y asumir que ese es el final de la discusión parece una forma segura de destruir el pensamiento crítico de los usuarios, junto con cualquier potencial para que el lenguaje evolucione, por ejemplo, más allá de las limitaciones de omisión o restricción artificial Hay demasiado "porque el Estándar dice" aquí, y no lo suficiente "y es muy sensato decir eso, debido a las siguientes razones prácticas". No sé por qué los votos positivos fluyen tan ansiosamente hacia las respuestas que solo dicen lo primero sin siquiera tratar de explorar lo último.
underscore_d
64

Las referencias no son objetos. No tienen almacenamiento propio, solo hacen referencia a objetos existentes. Por esta razón, no tiene sentido tener matrices de referencias.

Si desea un objeto liviano que haga referencia a otro objeto, puede usar un puntero. Solo podrá usar a structcon un miembro de referencia como objetos en matrices si proporciona una inicialización explícita para todos los miembros de referencia para todas las structinstancias. Las referencias no se pueden inicializar por defecto.

Editar: Como señala jia3ep, en la sección estándar sobre declaraciones hay una prohibición explícita sobre las matrices de referencias.

CB Bailey
fuente
66
Las referencias son iguales en su naturaleza que los punteros constantes y, por lo tanto, ocupan algo de memoria (almacenamiento) para señalar algo.
inazaruk
77
No necesariamente El compilador podría evitar almacenar la dirección, si es una referencia a un objeto local, por ejemplo.
EFraim
44
No necesariamente. Las referencias en estructuras usualmente ocupan algo de almacenamiento. Las referencias locales a menudo no. De cualquier manera, en el sentido estricto estándar, las referencias no son objetos y (esto era nuevo para mí) las referencias nombradas no son realmente variables .
CB Bailey
26
Sí, pero este es un detalle de implementación. Un objeto en el modelo C ++ es una región de almacenamiento escrita. Una referencia no es explícitamente un objeto y no hay garantía de que ocupe almacenamiento en ningún contexto en particular.
CB Bailey
9
Dicho de esta manera: puede tener una estructura de nada más que 16 referencias a foo, y usarla exactamente de la misma manera que me gustaría usar mi conjunto de referencias, excepto que no puede indexar en las 16 referencias foo . Esta es una verruga de lenguaje en mi humilde opinión.
greggo
28

Esta es una discusión interesante. Claramente, las matrices de referencias son completamente ilegales, pero en mi humilde opinión, la razón por la cual no es tan simple como decir 'no son objetos' o 'no tienen tamaño'. Señalaría que las matrices en sí mismas no son objetos completos en C / C ++: si se opone a eso, intente crear instancias de algunas clases de plantillas stl utilizando una matriz como parámetro de plantilla de 'clase' y vea qué sucede. No puede devolverlos, asignarlos, pasarlos como parámetros. (un parámetro de matriz se trata como un puntero). Pero es legal hacer matrices de matrices. Las referencias tienen un tamaño que el compilador puede y debe calcular: no puede sizeof () una referencia, pero puede hacer una estructura que contenga nada más que referencias. Tendrá un tamaño suficiente para contener todos los punteros que implementan las referencias. Usted puede'

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

De hecho, puede agregar esta línea a la definición de estructura

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

... y ahora tengo algo que se parece MUCHO a una serie de referencias:

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

Ahora, esto no es una matriz real, es una sobrecarga del operador; no hará cosas que las matrices normalmente hacen como sizeof (arr) / sizeof (arr [0]), por ejemplo. Pero hace exactamente lo que quiero que haga una serie de referencias, con C ++ perfectamente legal. Excepto (a) es difícil configurar más de 3 o 4 elementos, y (b) está haciendo un cálculo usando un montón de? , pero no obstante la indexación). Me gustaría ver un tipo muy limitado de "matriz de referencia" que realmente puede hacer esto. Es decir, una matriz de referencias no se trataría como una matriz general de cosas que son referencias, sino que sería una nueva 'matriz de referencia' hacer con plantillas).

Esto probablemente funcionaría, si no le importa este tipo de desagradable: reescriba '* this' como una matriz de int * 's y devuelva una referencia hecha desde uno: (no recomendado, pero muestra cómo la' matriz 'adecuada trabajaría):

 int & operator[]( int i ) { return *(reinterpret_cast<int**>(this)[i]); }
Greggo
fuente
1
Usted puede hacer esto en C ++ 11 std::tuple<int&,int&,int&> abcref = std::tie( a,b,c)- que crea una "matriz" de referencias que sólo pueden ser indexados por constantes de tiempo de compilación usando std :: get. Pero no puedes hacer std::array<int&,3> abcrefarr = std::tie( a,b,c). Tal vez hay una manera de hacerlo que no conozco. Me parece que debería haber una manera de escribir una especialización de la std::array<T&,N>cual se puede hacer esto, y por lo tanto una función std::tiearray(...)que devuelve uno de ellos (o permitir std::array<T&,N>aceptar el inicializador que contiene direcciones = {& v1, & v2, etc.)
greggo
Su propuesta es tonta (IMO), y su última línea asume que un int puede contener un puntero (generalmente falso, al menos donde trabajo). Pero esta es la única respuesta aquí con una justificación legítima de por qué las matrices de referencias están prohibidas, entonces +1
Nemo
@Nemo en realidad no fue una propuesta, sino solo para ilustrar que la funcionalidad de tal cosa no es absurda en su apariencia. Noto en mi último comentario que c ++ tiene cosas que se acercan. Además, la última línea a la que hace referencia asume que la memoria que implementa una referencia a un int puede ser útilmente alias como puntero a int (la estructura contiene referencias, no ints) y eso tiende a ser cierto. No estoy afirmando que sea portátil (o incluso a salvo de las reglas de "comportamiento indefinido"). tenía el propósito de ilustrar cómo la indexación se podía usar bajo el capó para evitar todo el problema?:
Greggo
Lo suficientemente justo. Pero sí creo que esta idea es "absurda", y esta es precisamente la razón por la cual no se permiten conjuntos de referencias. Una matriz es, ante todo, un contenedor. Todos los contenedores necesitan un tipo de iterador para que se apliquen los algoritmos estándar. El tipo de iterador para "matriz de T" es normalmente "T *". ¿Cuál sería el tipo de iterador para una matriz de referencias? La cantidad de pirateo de lenguaje que se necesitaría para lidiar con todos estos problemas es ridícula para una característica esencialmente inútil. De hecho, tal vez responderé por mi cuenta :-)
Nemo
@Nemo No necesita ser parte del lenguaje central, por lo que el tipo de iterador que es 'personalizado' no es gran cosa. Todos los diversos tipos de contenedor tienen sus tipos de iterador. En este caso, el iterador, desreferenciado, haría referencia a los objs de destino, no a las referencias en la matriz, lo cual es totalmente coherente con el comportamiento de la matriz. Todavía puede requerir un poco de nuevo comportamiento de C ++ para admitirlo como plantilla. El std::array<T,N>código muy simple y fácil de definir en la aplicación no se agregó a std :: hasta c ++ 11, presumiblemente porque es vergonzoso tener esto sin forma de ¿ = {1,2,3};Fue ese 'hackeo de idiomas'?
greggo
28

Comenta tu edición:

La mejor solución es std::reference_wrapper.

Detalles: http://www.cplusplus.com/reference/functional/reference_wrapper/

Ejemplo:

#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
fuente
Hola, esto se dijo en 09. Consejo buscar nuevas publicaciones :)
Stígandr
9
La publicación es antigua, pero no todos conocen la solución con reference_wrapper. Las personas necesitan un movimiento de lectura sobre STL y STDlib de C ++ 11.
Youw
Esto proporciona un enlace y un código de muestra, en lugar de solo mencionar el nombre de la clase reference_wrapper.
David Stone
12

Una matriz se puede convertir implícitamente en un puntero, y el puntero a referencia es ilegal en C ++

EFraim
fuente
11
Es cierto que no puede tener un puntero a una referencia, pero esta no es la razón por la que no puede tener una matriz de referencias. Más bien, ambos son síntomas del hecho de que las referencias no son objetos.
Richard Corden el
1
Una estructura no puede contener más que referencias, y tendrá un tamaño proporcional al número de ellas. Si tuviera una matriz de referencias 'arr', la conversión normal de matriz a puntero no tendría sentido, ya que 'puntero a referencia' no tiene sentido. pero tendría sentido usar arr [i], de la misma manera que st.ref cuando 'st' es una estructura que contiene una ref. Hmm Pero & arr [0] daría la dirección del primer objeto referenciado, y & arr [1] - & arr [0] no sería lo mismo que & arr [2] - & arr [1] - crearía mucha extrañeza.
greggo
11

Dado int& arr[] = {a,b,c,8};, ¿qué es sizeof(*arr)?

En todas partes, una referencia se trata simplemente como la cosa misma, por lo sizeof(*arr)que simplemente debería serlo sizeof(int). Pero esto haría que la aritmética del puntero de matriz en esta matriz sea incorrecta (suponiendo que las referencias no sean del mismo ancho que las ints). Para eliminar la ambigüedad, está prohibido.

PaulMurrayCbr
fuente
Sí. No puede tener una matriz de algo a menos que tenga un tamaño. ¿Cuál es el tamaño de una referencia?
David Schwartz
44
@DavidSchwartz ¿Hacer un structcuyo único miembro es esa referencia y averiguar ...?
underscore_d
3
Esta debería ser la respuesta aceptada, no la estúpida pedante que simplemente arroja el estándar al OP.
Tyson Jacobs
Tal vez hay un error tipográfico. "asumir que las referencias no tienen el mismo ancho es ints" debería ser "asumir que las referencias no tienen el mismo ancho que ints". El cambio es a como .
zhenguoli
Pero espera, según esta lógica, no podrías tener variables miembro de referencia.
Tyson Jacobs
10

Porque como muchos han dicho aquí, las referencias no son objetos. son simplemente alias. Es cierto que algunos compiladores pueden implementarlos como punteros, pero el estándar no fuerza / especifica eso. Y debido a que las referencias no son objetos, no puede señalarlas. Almacenar elementos en una matriz significa que hay algún tipo de dirección de índice (es decir, apuntando a elementos en un cierto índice); y es por eso que no puede tener matrices de referencias, porque no puede señalarlas.

Use boost :: reference_wrapper, o boost :: tuple en su lugar; o simplemente punteros.

navegador
fuente
5

Creo que la respuesta es muy simple y tiene que ver con reglas de referencias semánticas y cómo se manejan las matrices en C ++.

En resumen: las referencias pueden considerarse estructuras que no tienen un constructor predeterminado, por lo que se aplican las mismas reglas.

1) Semánticamente, las referencias no tienen un valor predeterminado. Las referencias solo se pueden crear haciendo referencia a algo. Las referencias no tienen un valor para representar la ausencia de una referencia.

2) Al asignar una matriz de tamaño X, el programa crea una colección de objetos inicializados por defecto. Como la referencia no tiene un valor predeterminado, crear una matriz de este tipo es semánticamente ilegal.

Esta regla también se aplica a estructuras / clases que no tienen un constructor predeterminado. El siguiente ejemplo de código no se compila:

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

Object objects[1]; // Error: no appropriate default constructor available
Kristupas A.
fuente
1
Puede crear una matriz de Object, sólo hay que asegurarse de inicializar todos ellos: Object objects[1] = {Object(42)};. Mientras tanto, no puede crear una serie de referencias, incluso si las inicializa todas.
Anton3
4

Puede acercarse bastante con esta estructura de plantilla. Sin embargo, debe inicializar con expresiones que son punteros a T, en lugar de T; y así, aunque puede hacer fácilmente un 'fake_constref_array' de manera similar, no podrá vincularlo a los valores como se hizo en el ejemplo del 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
fuente
2

Un objeto de referencia no tiene tamaño. Si escribe sizeof(referenceVariable), le dará el tamaño del objeto al que hace referencia referenceVariable, no el de la referencia en sí. No tiene un tamaño propio, por lo que el compilador no puede calcular cuánto tamaño requeriría la matriz.

Carl Seleborg
fuente
3
Si es así, ¿cómo puede un compilador calcular el tamaño de una estructura que contiene solo una referencia?
Juster
El compilador conoce el tamaño físico de una referencia, es lo mismo que un puntero. Las referencias son, de hecho, punteros semánticamente glorificados. La diferencia entre los punteros y las referencias es que las referencias tienen bastantes reglas semánticas para reducir la cantidad de errores que puede crear en comparación con los punteros.
Kristupas A.
2

Cuando almacena algo en una matriz, su tamaño debe ser conocido (ya que la indexación de la matriz depende del tamaño). Según el estándar de C ++ No se especifica si una referencia requiere almacenamiento o no, por lo que no sería posible indexar una matriz de referencias.

Pradyot
fuente
1
Pero al poner dicha referencia en a struct, mágicamente todo se vuelve especificado, bien definido, garantizado y de tamaño determinista. ¿Por qué, por lo tanto, existe esta diferencia aparentemente totalmente artificial?
underscore_d
... por supuesto, si uno realmente comienza a pensar en lo que structconfiere el envoltorio , algunas buenas respuestas posibles comienzan a sugerir, pero para mí, nadie lo ha hecho aún, a pesar de que este es uno de los desafíos inmediatos para todos los comunes razones por las cuales no puedes usar referencias sin envolver.
underscore_d
2

Solo para agregar a toda la conversación. Dado que las matrices requieren ubicaciones de memoria consecutivas para almacenar el elemento, por lo tanto, si creamos una matriz de referencias, no se garantiza que estén en una ubicación de memoria consecutiva, por lo que el acceso será un problema y, por lo tanto, ni siquiera podemos aplicar todas las operaciones matemáticas en formación.

Amit Kumar
fuente
Considere el siguiente código de la pregunta original: int a = 1, b = 2, c = 3; int & arr [] = {a, b, c, 8}; Hay muchas menos posibilidades de que a, b, c residan en una ubicación de memoria consecutiva.
Amit Kumar
Eso está lejos del problema principal con el concepto de contener referencias en un contenedor.
underscore_d
0

Considere una variedad de punteros. Un puntero es realmente una dirección; así que cuando inicializa la matriz, le está diciendo de manera análoga a la computadora, "asigne este bloque de memoria para contener estos números X (que son direcciones de otros elementos)". Entonces, si cambia uno de los punteros, solo está cambiando lo que apunta; sigue siendo una dirección numérica que se encuentra en el mismo lugar.

Una referencia es análoga a un alias. Si declarara una serie de referencias, básicamente le estaría diciendo a la computadora, "asigne esta gota de memoria amorfa que consiste en todos estos elementos diferentes dispersos".

Dan Tao
fuente
1
¿Por qué, entonces, hacer un structcuyo único miembro es una referencia resulta en algo completamente legal, predecible y no amorfo? ¿Por qué un usuario debe ajustar una referencia para obtener mágicamente poderes con capacidad de matriz, o es solo una restricción artificial? En mi opinión, ninguna respuesta ha abordado directamente esto. Supongo que la refutación es 'El objeto de ajuste asegura que puede usar la semántica de valores, en la forma del objeto de ajuste, por lo que asegura que puede tener las garantías de valores, que son necesarias porque, por ejemplo, ¿cómo se podría desambiguar entre el elemento y la dirección del árbitro'? ... ¿Es eso lo que querías decir?
underscore_d
-2

En realidad, esta es una mezcla de sintaxis C y C ++.

Usted debe o bien utilizar matrices C puros, que no puede ser de referencias, ya que son parte de referencia C ++ solamente. O sigue el camino de C ++ y usa la clase std::vectoro std::arraypara su propósito.

En cuanto a la parte editada: aunque structes un elemento de C, usted define un constructor y funciones de operador, que lo convierten en C ++ class. En consecuencia, su structno compilaría en C puro!

Thargon
fuente
¿Cual es tu punto? std::vectoro std::arraytampoco puede contener referencias. El estándar C ++ es bastante explícito que ningún contenedor puede.
underscore_d