C ++ Diferencia entre std :: ref (T) y T &?

92

Tengo algunas preguntas sobre este programa:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
template <typename T> void foo ( T x )
{
    auto r=ref(x);
    cout<<boolalpha;
    cout<<is_same<T&,decltype(r)>::value;
}
int main()
{
    int x=5;
    foo (x);
    return 0;
}

La salida es:

false

Quiero saber, si std::refno devuelve la referencia de un objeto, ¿qué hace? Básicamente, ¿cuál es la diferencia entre:

T x;
auto r = ref(x);

y

T x;
T &y = x;

Además, quiero saber por qué existe esta diferencia. ¿Por qué necesitamos std::refo std::reference_wrappercuando tenemos referencias (es decir T&)?

CppNITR
fuente
2
Posible duplicado de ¿Cómo es útil tr1 :: reference_wrapper?
anderas
Pista: ¿qué pasa si lo hace x = y; en ambos casos?
juanchopanza
2
Además de mi bandera duplicada (último comentario): consulte, por ejemplo, stackoverflow.com/questions/31270810/… y stackoverflow.com/questions/26766939/…
anderas
2
@anderas no se trata de la utilidad, se trata principalmente de la diferencia
CppNITR
@CppNITR Luego, vea las preguntas que vinculé unos segundos antes de su comentario. Especialmente el segundo es útil.
anderas

Respuestas:

91

Well refconstruye un objeto del reference_wrappertipo apropiado para contener una referencia a un objeto. Lo que significa que cuando aplicas:

auto r = ref(x);

Esto devuelve a reference_wrappery no una referencia directa a x(ie T&). En cambio, esto reference_wrapper(es decir r) se mantiene T&.

Una reference_wrapperes muy útil cuando se desea emular una referencede un objeto que se puede copiar (que es a la vez -construible copia y copiar asignable ).

En C ++, una vez que crea una referencia (digamos y) a un objeto (digamos x), entonces yy xcomparte la misma dirección base . Además, yno puede referirse a ningún otro objeto. Además, no puede crear una matriz de referencias, es decir, un código como este arrojará un error:

#include <iostream>
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    int& arr[] {x,y,z};    // error: declaration of 'arr' as array of references
    return 0;
}

Sin embargo, esto es legal:

#include <iostream>
#include <functional>  // for reference_wrapper
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    reference_wrapper<int> arr[] {x,y,z};
    for (auto a: arr)
        cout << a << " ";
    return 0;
}
/* OUTPUT:
5 7 8
*/

Hablando de tu problema con cout << is_same<T&,decltype(r)>::value;, la solución es:

cout << is_same<T&,decltype(r.get())>::value;  // will yield true

Déjame mostrarte un programa:

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

int main()
{
    cout << boolalpha;
    int x=5, y=7;
    reference_wrapper<int> r=x;   // or auto r = ref(x);
    cout << is_same<int&, decltype(r.get())>::value << "\n";
    cout << (&x==&r.get()) << "\n";
    r=y;
    cout << (&y==&r.get()) << "\n";
    r.get()=70;
    cout << y;
    return 0;
}
/* Ouput:
true
true
true
70
*/

Mira aquí llegamos a saber tres cosas:

  1. Un reference_wrapperobjeto (aquí r) se puede usar para crear una matriz de referencias que no era posible con T&.

  2. ren realidad actúa como una referencia real (vea cómo r.get()=70cambió el valor de y).

  3. rno es lo mismo que T&pero r.get()es. Esto significa que se rmantiene, T&es decir, como sugiere su nombre, es un envoltorio alrededor de una referencia T& .

Espero que esta respuesta sea más que suficiente para explicar tus dudas.

Ankit Acharya
fuente
3
1: No, reference_wrapperse puede reasignar a , pero no puede "contener referencia a más de un objeto". 2/3: punto justo sobre dónde .get()es apropiado, pero sin sufijo r se puede usar de la misma manera que T&en los casos en rlos que la conversión operatorse puede invocar de manera inequívoca, por lo que no es necesario llamar .get()en muchos casos, incluidos varios en su código (que es difícil de leer por falta de espacios).
underscore_d
@underscore_d reference_wrapperpuede contener una serie de referencias si no está seguro, puede probarlo usted mismo. Plus .get()se usa cuando desea cambiar el valor del objeto que reference_wrapperestá sosteniendo, r=70es decir, es ilegal, por lo que debe usar r.get()=70. Pruébelo usted mismo !!!!!!
Ankit Acharya
1
Realmente no. Primero, usemos una redacción precisa. Estás mostrando un reference_wrapper que contiene una referencia a una matriz , no un reference_wrapper que en sí mismo contiene "referencia a más de un objeto" . El contenedor solo contiene una referencia. En segundo lugar, puedo obtener una referencia nativa a una matriz muy bien: ¿está seguro de que no se olvidó de los (paréntesis) int a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper no es especial aquí ya que el nativo T& funciona.
underscore_d
1
@AnkitAcharya Sí :-) pero para ser preciso, eficaz resultado a un lado, en sí sólo se refiere a un objeto. De todos modos, por supuesto que tiene razón en que, a diferencia de un árbitro normal, wrapperpuede ir en un recipiente. Esto es útil, pero creo que la gente lo malinterpreta como más avanzado de lo que realmente es. Si quiero una serie de 'referencias', generalmente me salto al intermediario vector<Item *>, que es a lo que se wrapperreduce ... y espero que los puristas anti-punteros no me encuentren. Los casos de uso convincentes son diferentes y más complejos.
underscore_d
1
"Un reference_wrapper es muy útil cuando desea emular una referencia de un objeto que se puede copiar". ¿Pero eso no frustra el propósito de una referencia? Te refieres a la cosa real. Eso es lo que ES una referencia. Una referencia que se puede copiar, parece inútil, porque si quieres copiarla, entonces no quieres una referencia en primer lugar, solo debes copiar el objeto original. Parece una capa innecesaria de complejidad para resolver un problema que no necesita existir en primer lugar.
Stu
53

std::reference_wrapper es reconocido por las instalaciones estándar para poder pasar objetos por referencia en contextos de paso por valor.

Por ejemplo, std::bindpuede std::ref()incorporar a algo, transmitirlo por valor y descomprimirlo en una referencia más adelante.

void print(int i) {
    std::cout << i << '\n';
}

int main() {
    int i = 10;

    auto f1 = std::bind(print, i);
    auto f2 = std::bind(print, std::ref(i));

    i = 20;

    f1();
    f2();
}

Este fragmento da como resultado:

10
20

El valor de ise ha almacenado (tomado por valor) en f1el punto en el que se inicializó, pero f2ha mantenido un std::reference_wrappervalor por valor y, por lo tanto, se comporta como si hubiera tomado un valor int&.

Quentin
fuente
2
@CppNITR seguro! Dame un momento para armar una pequeña demostración :)
Quentin
1
¿Qué pasa con la diferencia entre T & & ref (T)
CppNITR
3
@CppNITR std::ref(T)devuelve un std::reference_wrapper. Es poco más que un puntero envuelto, pero la biblioteca lo reconoce como "¡Oye, se supone que debo ser una referencia! Por favor, conviérteme en uno una vez que hayas terminado de pasarme".
Quentin
38

Una referencia ( T&o T&&) es un elemento especial en el lenguaje C ++. Permite manipular un objeto por referencia y tiene casos de uso especiales en el lenguaje. Por ejemplo, no puede crear un contenedor estándar para contener referencias: vector<T&>está mal formado y genera un error de compilación.

A, std::reference_wrapperpor otro lado, es un objeto C ++ capaz de contener una referencia. Como tal, puede usarlo en contenedores estándar.

std::refes una función estándar que devuelve un std::reference_wrapperen su argumento. En la misma idea, std::crefvuelve std::reference_wrappera una referencia constante.

Una propiedad interesante de a std::reference_wrapper, es que tiene un operator T& () const noexcept;. Eso significa que incluso si es un objeto verdadero , se puede convertir automáticamente a la referencia que contiene. Entonces:

  • al ser un objeto asignable por copia, se puede utilizar en contenedores o en otros casos donde no se permiten referencias
  • gracias a operator T& () const noexcept;él, se puede usar en cualquier lugar donde pueda usar una referencia, ya que se convertirá automáticamente a ella.
Serge Ballesta
fuente
1
votó a favor principalmente porque se mencionó operator T& ()que otras 2 respuestas no mencionaron.
metablaster