¿Qué significa "int & foo ()" en C ++?

114

Mientras leía esta explicación sobre lvalues ​​y rvalues, estas líneas de código me llamaron la atención:

int& foo();
foo() = 42; // OK, foo() is an lvalue

Lo intenté en g ++, pero el compilador dice "referencia indefinida a foo ()". Si agrego

int foo()
{
  return 2;
}

int main()
{
  int& foo();
  foo() = 42;
}

Se compila bien, pero ejecutarlo produce un error de segmentación . Solo la linea

int& foo();

por sí mismo se compila y se ejecuta sin problemas.

¿Qué significa este código? ¿Cómo se puede asignar un valor a una llamada de función y por qué no es un rvalue?

Sossisos
fuente
25
No está asignando un valor a una llamada de función , está asignando un valor a la referencia que devuelve .
Frédéric Hamidi
3
@ FrédéricHamidi asigna un valor al objeto al que se refiere la referencia devuelta
MM
3
Sí, es un alias para el objeto; la referencia no es el objeto
MM
2
Las declaraciones de funciones locales de @RoyFalk son una viruela, personalmente me alegraría verlas eliminadas del idioma. (Excluyendo lambdas, por supuesto)
MM
4
Ya hay un error de GCC para esto .
jotik

Respuestas:

168

La explicación asume que existe alguna implementación razonable para la foocual devuelve una referencia lvalue a un valor válido int.

Tal implementación podría ser:

int a = 2; //global variable, lives until program termination

int& foo() {
    return a;
} 

Ahora, dado que foodevuelve una referencia lvalue, podemos asignar algo al valor de retorno, así:

foo() = 42;

Esto actualizará el global acon el valor 42, que podemos verificar accediendo a la variable directamente o llamando foonuevamente:

int main() {
    foo() = 42;
    std::cout << a;     //prints 42
    std::cout << foo(); //also prints 42
}
TartánLlama
fuente
¿Razonable como en el operador []? ¿O algún otro método de acceso para miembros?
Jan Dorniak
8
Dada la confusión sobre las referencias, probablemente sea mejor eliminar las variables locales estáticas de las muestras. La inicialización agrega una complejidad innecesaria, que es un obstáculo para el aprendizaje. Simplemente hazlo global.
Mooing Duck
72

Todas las demás respuestas declaran una estática dentro de la función. Creo que eso podría confundirte, así que mira esto:

int& highest(int  & i, int  & j)
{
    if (i > j)
    {
        return i;
    }
    return j;
}

int main()
{
    int a{ 3};
    int b{ 4 };
    highest(a, b) = 11;
    return 0;
}

Como highest()devuelve una referencia, puede asignarle un valor. Cuando se ejecute, bse cambiará a 11. Si cambió la inicialización para que afuera, digamos, 8, entonces ase cambiaría a 11. Este es un código que en realidad podría tener un propósito, a diferencia de los otros ejemplos.

Kate Gregory
fuente
5
¿Hay alguna razón para escribir int a {3} en lugar de int a = 3? Esta sintaxis no me parece apropiada.
Aleksei Petrenko
6
@AlexPetrenko eso es inicialización universal. Lo estaba usando en el ejemplo que tenía a mano. Nada inapropiado al respecto
Kate Gregory
3
Yo sé lo que es esto. a = 3 se siente mucho más limpio. Preferencia personal)
Aleksei Petrenko
Al igual que declarar estática dentro de una función puede confundir a algunas personas, también creo que usar la inicialización universal es probable que lo haga.
Ericcurtin
@AlekseiPetrenko En el caso de int a = 1.3, implícitamente se convierte en a = 1. Mientras que int a {1.3} da como resultado un error: reducción de la conversión.
Sathvik
32
int& foo();

Declara una función llamada foo que devuelve una referencia a un int. Lo que esos ejemplos no consiguen es darte una definición de esa función que podrías compilar. Si usamos

int & foo()
{
    static int bar = 0;
    return bar;
}

Ahora tenemos una función que devuelve una referencia a bar. dado que bar es static, seguirá vivo después de la llamada a la función, por lo que devolver una referencia a ella es seguro. Ahora si lo hacemos

foo() = 42;

Lo que sucede es que asignamos 42 a barya que asignamos a la referencia y la referencia es solo un alias para bar. Si volvemos a llamar a la función como

std::cout << foo();

Imprimiría 42 ya que lo establecimos bararriba.

NathanOliver
fuente
15

int &foo();declara una función llamada foo()con tipo de retornoint& . Si llama a esta función sin proporcionar un cuerpo, es probable que obtenga un error de referencia indefinido.

En su segundo intento proporcionó una función int foo(). Tiene un tipo de retorno diferente al de la función declarada por int& foo();. Por lo tanto, tiene dos declaraciones de la misma fooque no coinciden, lo que viola la regla de una definición y causa un comportamiento indefinido (no se requiere diagnóstico).

Para algo que funcione, saque la declaración de función local. Pueden conducir a un comportamiento silencioso indefinido como ha visto. En su lugar, solo use declaraciones de función fuera de cualquier función. Su programa podría verse así:

int &foo()
{
    static int i = 2;
    return i;
}  

int main()
{
    ++foo();  
    std::cout << foo() << '\n';
}
MM
fuente
10

int& foo();es una función que devuelve una referencia a int. Su función proporcionada regresa intsin referencia.

Puedes hacer

int& foo()
{
    static int i = 42;
    return i;
}

int main()
{
    int& foo();
    foo() = 42;
}
Jarod42
fuente
7

int & foo();significa que foo()devuelve una referencia a una variable.

Considere este código:

#include <iostream>
int k = 0;

int &foo()
{
    return k;
}

int main(int argc,char **argv)
{
    k = 4;
    foo() = 5;
    std::cout << "k=" << k << "\n";
    return 0;
}

Este código imprime:

$ ./a.out k = 5

Porque foo()devuelve una referencia a la variable global k.

En su código revisado, está enviando el valor devuelto a una referencia, que luego no es válida.

vy32
fuente
5

En ese contexto, & significa una referencia, por lo que foo devuelve una referencia a un int, en lugar de un int.

No estoy seguro de si hubiera trabajado con punteros todavía, pero es una idea similar, en realidad no está devolviendo el valor de la función, sino que está pasando la información necesaria para encontrar la ubicación en la memoria donde eso int es.

Entonces, para resumir, no está asignando un valor a una llamada de función; está usando una función para obtener una referencia y luego asigna el valor al que se hace referencia a un nuevo valor. Es fácil pensar que todo sucede a la vez, pero en realidad la computadora hace todo en un orden preciso.

Si se está preguntando, la razón por la que está obteniendo un error de segmento es porque está devolviendo un literal numérico '2', por lo que es el error exacto que obtendría si definiera un int const y luego intentara modificar su valor.

Si aún no ha aprendido acerca de los punteros y la memoria dinámica, le recomendaría eso primero, ya que hay algunos conceptos que creo que son difíciles de entender a menos que los esté aprendiendo todos a la vez.

Dar Brett
fuente
4

El código de ejemplo en la página vinculada es solo una declaración de función ficticia. No se compila, pero si tuviera alguna función definida, funcionaría en general. El ejemplo significaba "Si tuvieras una función con esta firma, podrías usarla así".

En su ejemplo, fooclaramente devuelve un lvalue basado en la firma, pero devuelve un rvalue que se convierte en un lvalue. Esto claramente está decidido a fallar. Podrías hacerlo:

int& foo()
{
    static int x;
    return x;
}

y tendría éxito cambiando el valor de x, al decir:

foo() = 10;
Hielo fuego
fuente
3

La función que tiene, foo (), es una función que devuelve una referencia a un número entero.

Entonces, digamos que originalmente foo devolvió 5, y más adelante, en su función principal, dice foo() = 10;, luego imprime foo, imprimirá 10 en lugar de 5.

Espero que tenga sentido :)

También soy nuevo en la programación. ¡Es interesante ver preguntas como esta que te hacen pensar! :)

psj01
fuente