¿Por qué no puedo hacer un vector de referencias?

351

Cuando hago esto:

std::vector<int> hello;

Todo funciona muy bien. Sin embargo, cuando lo convierto en un vector de referencias en su lugar:

std::vector<int &> hello;

Recibo errores horribles como

error C2528: 'puntero': el puntero a referencia es ilegal

Quiero poner un montón de referencias a estructuras en un vector, para no tener que entrometerme con punteros. ¿Por qué el vector está haciendo un berrinche sobre esto? ¿Es mi única opción usar un vector de punteros en su lugar?

Colen
fuente
46
puede usar std :: vector <reference_wrapper <int>> hello; Ver informit.com/guides/content.aspx?g=cplusplus&seqNum=217
amit
2
@amit el enlace ya no es válido, documentación oficial aquí
Martin

Respuestas:

339

El tipo de componente de los contenedores, como los vectores, debe ser asignable . Las referencias no son asignables (solo puede inicializarlas una vez cuando se declaran, y no puede hacer que hagan referencia a otra cosa más adelante). Otros tipos no asignables tampoco están permitidos como componentes de contenedores, por ejemplo, vector<const int>no está permitido.

newacct
fuente
1
¿Estás diciendo que no puedo tener un vector de vectores? (Estoy seguro de que he hecho eso ...)
James Curran
8
Sí, un std :: vector <std :: vector <int>> es correcto, std :: vector es asignable.
Martin Cote
17
De hecho, esta es la razón "real". El error acerca de que T * es imposible de T es U y es solo un efecto secundario del requisito violado de que T debe ser asignable. Si el vector pudo verificar con precisión el parámetro de tipo, entonces probablemente diría "requisito violado: T & no asignable"
Johannes Schaub - litb
2
Verificando el concepto asignable en boost.org/doc/libs/1_39_0/doc/html/Assignable.html todas las operaciones excepto el intercambio son válidas en las referencias.
amit
77
Esto ya no es verdad. Desde C ++ 11, el único requisito de elemento independiente de la operación es ser "borrable", y la referencia no lo es. Ver stackoverflow.com/questions/33144419/… .
laike9m
119

Sí, puede, busque std::reference_wrapper, que imita una referencia pero es asignable y también puede ser "reubicado"

Ion Todirel
fuente
44
¿Hay alguna forma de evitar las llamadas get()primero al intentar acceder a un método de una instancia de una clase en este contenedor? Por ejemplo, reference_wrapper<MyClass> my_ref(...); my_ref.get().doStuff();no es muy referencia como.
Timdiels
3
¿No se puede convertir implícitamente al Tipo en sí mismo al devolver la referencia?
WorldSEnder
3
Sí, pero eso requiere un contexto que implique qué conversión se requiere. El acceso de los miembros no hace eso, de ahí la necesidad de hacerlo .get(). Lo que Timdiels quiere es operator.; Eche un vistazo a las últimas propuestas / discusiones sobre eso.
underscore_d
31

Por su propia naturaleza, las referencias solo se pueden establecer en el momento en que se crean; es decir, las siguientes dos líneas tienen efectos muy diferentes:

int & A = B;   // makes A an alias for B
A = C;         // assigns value of C to B.

Además, esto es ilegal:

int & D;       // must be set to a int variable.

Sin embargo, cuando crea un vector, no hay forma de asignar valores a sus elementos en la creación. Básicamente solo estás haciendo un montón del último ejemplo.

James Curran
fuente
10
"cuando crea un vector, no hay forma de asignar valores a sus elementos en la creación" No entiendo lo que quiere decir con esta declaración. ¿Cuáles son "sus artículos en la creación"? Puedo crear un vector vacío. Y puedo agregar elementos con .push_back (). Solo está señalando que las referencias no son construibles por defecto. Pero definitivamente puedo tener vectores de clases que no son construibles por defecto.
newacct
3
No se requiere que el elemento ype de std :: vector <T> sea por defecto construible. Puede escribir struct A {A (int); privado: A (); }; vector <A> a; bien, siempre y cuando no utilice dichos métodos que requieren que sea constructible por defecto (como v.resize (100); - pero en su lugar deberá hacer v.resize (100, A (1));)
Johannes Schaub - litb
¿Y cómo escribirías un push_back () en este caso? Seguirá utilizando asignación, no construcción.
James Curran
44
James Curran, No se realiza ninguna construcción predeterminada allí. push_back solo ubicación-noticias A en el búfer preasignado. Ver aquí: stackoverflow.com/questions/672352/… . Tenga en cuenta que mi reclamo es solo que el vector puede manejar tipos construibles no predeterminados. No pretendo, por supuesto, que pueda manejar T & (no puede, por supuesto).
Johannes Schaub - litb
29

Ion Todirel ya mencionó una respuesta usando std::reference_wrapper. Desde C ++ 11 tenemos un mecanismo para recuperar objetos std::vector y eliminar la referencia mediante el uso std::remove_reference. A continuación se muestra un ejemplo compilado usando g++y clangcon la opción
-std=c++11y ejecutado con éxito.

#include <iostream>
#include <vector>
#include<functional>

class MyClass {
public:
    void func() {
        std::cout << "I am func \n";
    }

    MyClass(int y) : x(y) {}

    int getval()
    {
        return x;
    }

private: 
        int x;
};

int main() {
    std::vector<std::reference_wrapper<MyClass>> vec;

    MyClass obj1(2);
    MyClass obj2(3);

    MyClass& obj_ref1 = std::ref(obj1);
    MyClass& obj_ref2 = obj2;

    vec.push_back(obj_ref1);
    vec.push_back(obj_ref2);

    for (auto obj3 : vec)
    {
        std::remove_reference<MyClass&>::type(obj3).func();      
        std::cout << std::remove_reference<MyClass&>::type(obj3).getval() << "\n";
    }             
}
Steephen
fuente
12
No veo el valor std::remove_reference<>aquí. El punto std::remove_reference<>es permitirle escribir "el tipo T, pero sin ser una referencia si es uno". Entonces std::remove_reference<MyClass&>::typees lo mismo que escribir MyClass.
alastair
2
No hay ningún valor en eso: solo puede escribir for (MyClass obj3 : vec) std::cout << obj3.getval() << "\n"; (o for (const MyClass& obj3: vec)si declara getval()const, como debería).
Toby Speight
14

boost::ptr_vector<int> trabajará.

Editar: fue una sugerencia de uso std::vector< boost::ref<int> >, que no funcionará porque no se puede construir por defecto a boost::ref.

Drew Dormann
fuente
8
Pero puedes tener un vector o tipos no construibles por defecto, ¿verdad? Solo debe tener cuidado de no utilizar el ctor predeterminado. del vector
Manuel
1
@Manuel: O resize.
Carreras ligeras en órbita el
55
Tenga cuidado, los contenedores de puntero de Boost son propiedad exclusiva de los pointees. Cita : "Cuando necesita semántica compartida, esta biblioteca no es lo que necesita".
Matthäus Brandl
12

Es una falla en el lenguaje C ++. No puede tomar la dirección de una referencia, ya que intentar hacerlo daría lugar a la dirección del objeto al que se hace referencia y, por lo tanto, nunca puede obtener un puntero a una referencia. std::vectorfunciona con punteros a sus elementos, por lo que los valores que se almacenan deben poder señalarse. Tendrás que usar punteros en su lugar.

Adam Rosenfield
fuente
Supongo que podría implementarse usando un búfer void * y una nueva ubicación. No es que esto tenga mucho sentido.
peterchen
55
"Defecto en el idioma" es demasiado fuerte. Es por diseño. No creo que sea necesario que el vector funcione con punteros a elementos. Sin embargo, se requiere que el elemento sea asignable. Las referencias no lo son.
Brian Neal
Tampoco puedes tomar sizeofuna referencia.
David Schwartz
1
no necesita un puntero a una referencia, tiene la referencia, si necesita el puntero, simplemente mantenga el puntero en sí; no hay problema para resolver aquí
Ion Todirel
10

TL; DR

Usar std::reference_wrapperasí:

#include <functional>
#include <string>
#include <vector>
#include <iostream>

int main()
{
    std::string hello = "Hello, ";
    std::string world = "everyone!";
    typedef std::vector<std::reference_wrapper<std::string>> vec_t;
    vec_t vec = {hello, world};
    vec[1].get() = "world!";
    std::cout << hello << world << std::endl;
    return 0;
}

Demo

Respuesta larga

Como sugiere el estándar , para un contenedor estándar que Xcontiene objetos de tipo T, Tdebe ser Erasablede X.

Erasable significa que la siguiente expresión está bien formada:

allocator_traits<A>::destroy(m, p)

Aes el tipo de asignador del contenedor, mes la instancia del asignador y pes un puntero de tipo *T. Ver aquí para la Erasabledefinición.

Por defecto, std::allocator<T>se utiliza como asignador de vectores. Con el asignador predeterminado, el requisito es equivalente a la validez de p->~T()(Tenga en cuenta que Tes un tipo de referencia y papunta a una referencia). Sin embargo, el puntero a una referencia es ilegal , por lo tanto, la expresión no está bien formada.

ivaigult
fuente
3

Como otros han mencionado, probablemente terminarás usando un vector de punteros en su lugar.

Sin embargo, ¡puede considerar usar un ptr_vector en su lugar!

Martin Cote
fuente
44
Esta respuesta no es viable, ya que se supone que un ptr_vector es un almacenamiento. Es decir, eliminará los punteros en la eliminación. Por lo tanto, no es utilizable para su propósito.
Klemens Morgenstern
0

Como sugieren los otros comentarios, está limitado a usar punteros. Pero si ayuda, aquí hay una técnica para evitar enfrentar directamente con punteros.

Puedes hacer algo como lo siguiente:

vector<int*> iarray;
int default_item = 0; // for handling out-of-range exception

int& get_item_as_ref(unsigned int idx) {
   // handling out-of-range exception
   if(idx >= iarray.size()) 
      return default_item;
   return reinterpret_cast<int&>(*iarray[idx]);
}
Omid
fuente
reinterpret_castno es necesario
Xeverous
1
Como muestran las otras respuestas, no estamos limitados a usar punteros en absoluto.
underscore_d