O que “int & foo ()” significa em C ++?

114

Ao ler esta explicação sobre lvalues ​​e rvalues, estas linhas de código ficaram grudadas em mim:

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

Eu tentei em g ++, mas o compilador diz "referência indefinida para foo ()". Se eu adicionar

int foo()
{
  return 2;
}

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

Ele compila bem, mas executá-lo causa uma falha de segmentação . Apenas a linha

int& foo();

por si só compila e executa sem problemas.

O que este código significa? Como você pode atribuir um valor a uma chamada de função e por que não é um rvalue?

Sossisos
fonte
25
Você não está atribuindo um valor a uma chamada de função , mas atribuindo um valor à referência que ela retorna .
Frédéric Hamidi,
3
@ FrédéricHamidi atribuindo um valor ao objeto ao qual a referência retornada se refere
MM
3
Sim, é um apelido para o objeto; a referência não é o objeto
MM
2
As declarações de funções locais do @RoyFalk são uma desgraça, pessoalmente ficaria feliz em vê-las removidas da linguagem. (Excluindo lambdas, é claro)
MM de
4
Já existe um bug do GCC para isso .
jotik

Respostas:

168

A explicação pressupõe que haja alguma implementação razoável para a fooqual retorna uma referência lvalue para um válido int.

Essa implementação pode ser:

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

int& foo() {
    return a;
} 

Agora, como fooretorna uma referência de lvalue, podemos atribuir algo ao valor de retorno, como:

foo() = 42;

Isso atualizará o global acom o valor 42, que podemos verificar acessando a variável diretamente ou chamando foonovamente:

int main() {
    foo() = 42;
    std::cout << a;     //prints 42
    std::cout << foo(); //also prints 42
}
TartanLlama
fonte
Razoável como no operador []? Ou algum outro método de acesso de membro?
Jan Dorniak
8
Dada a confusão sobre referências, provavelmente é melhor remover as variáveis ​​locais estáticas das amostras. A inicialização adiciona complexidade desnecessária, o que é um obstáculo para o aprendizado. Basta torná-lo global.
Mooing Duck
72

Todas as outras respostas declaram uma estática dentro da função. Acho que isso pode confundi-lo, então dê uma olhada nisto:

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()retorna uma referência, você pode atribuir um valor a ela. Quando for executado, bserá alterado para 11. Se você alterou a inicialização para que afosse, digamos, 8, então aseria alterado para 11. Este é um código que pode realmente servir a um propósito, ao contrário dos outros exemplos.

Kate Gregory
fonte
5
Existe uma razão para escrever int a {3} em vez de int a = 3? Essa sintaxe não me parece apropriada.
Aleksei Petrenko,
6
@AlexPetrenko é a inicialização universal. Acontece que eu estava usando no exemplo que tinha à mão. Nada impróprio sobre isso
Kate Gregory
3
Eu sei o que é isso a = 3 parece muito mais limpo. Preferência pessoal)
Aleksei Petrenko,
Assim como declarar estático dentro de uma função pode confundir algumas pessoas, eu também acredito que usar a inicialização universal provavelmente o fará.
ericcurtin
@AlekseiPetrenko No caso de int a = 1,3, ele implicitamente converte para a = 1. Enquanto que int a {1,3} resulta em erro: redução da conversão.
Sathvik
32
int& foo();

Declara uma função chamada foo que retorna uma referência a um int. O que esse exemplo falha em fornecer a você uma definição dessa função que você poderia compilar. Se usarmos

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

Agora temos uma função que retorna uma referência para bar. como bar é, staticele permanecerá ativo após a chamada à função, portanto, retornar uma referência a ele é seguro. Agora se fizermos

foo() = 42;

O que acontece é que atribuímos 42 a, barpois atribuímos à referência e a referência é apenas um apelido para bar. Se chamarmos a função novamente como

std::cout << foo();

Ele imprimiria 42, já que definimos barcomo acima.

NathanOliver
fonte
15

int &foo();declara uma função chamada foo()com tipo de retorno int&. Se você chamar essa função sem fornecer um corpo, provavelmente obterá um erro de referência indefinida.

Em sua segunda tentativa, você forneceu uma função int foo(). Isso tem um tipo de retorno diferente para a função declarada por int& foo();. Portanto, você tem duas declarações iguais fooque não correspondem, o que viola a regra de definição única causando comportamento indefinido (nenhum diagnóstico necessário).

Para algo que funcione, tire a declaração da função local. Eles podem levar a um comportamento silencioso e indefinido, como você viu. Em vez disso, use apenas declarações de função fora de qualquer função. Seu programa pode ser semelhante a:

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

int main()
{
    ++foo();  
    std::cout << foo() << '\n';
}
MILÍMETROS
fonte
10

int& foo();é uma função que retorna uma referência para int. Sua função fornecida retorna intsem referência.

Você pode fazer

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

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

int & foo();significa que foo()retorna uma referência a uma variável.

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()retorna uma referência à variável global k.

Em seu código revisado, você está convertendo o valor retornado em uma referência, que então é inválida.

vy32
fonte
5

Nesse contexto, o & significa uma referência - então foo retorna uma referência a um int, ao invés de um int.

Não tenho certeza se você já trabalhou com ponteiros, mas é uma ideia semelhante, você não está realmente retornando o valor da função - em vez disso, você está passando as informações necessárias para encontrar o local na memória onde int é.

Portanto, para resumir, você não está atribuindo um valor a uma chamada de função - você está usando uma função para obter uma referência e, em seguida, atribuindo o valor referenciado a um novo valor. É fácil pensar que tudo acontece ao mesmo tempo, mas na realidade o computador faz tudo em uma ordem precisa.

Se você está se perguntando - o motivo de estar obtendo um segfault é porque está retornando um literal numérico '2' - então é o erro exato que você obteria se definisse um const int e depois tentasse modificá-lo valor.

Se você ainda não aprendeu sobre ponteiros e memória dinâmica, recomendo isso primeiro, pois há alguns conceitos que acho difíceis de entender, a menos que você os aprenda todos de uma vez.

Dar Brett
fonte
4

O código de exemplo na página vinculada é apenas uma declaração de função fictícia. Não compila, mas se você tivesse alguma função definida, funcionaria de maneira geral. O exemplo significava "Se você tivesse uma função com esta assinatura, poderia usá-la assim".

Em seu exemplo, fooestá claramente retornando um lvalue com base na assinatura, mas você retorna um rvalue que é convertido em um lvalue. Isso está claramente determinado a falhar. Você poderia fazer:

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

e teria sucesso alterando o valor de x, ao dizer:

foo() = 10;
Gelo fogo
fonte
3

A função que você tem, foo (), é uma função que retorna uma referência a um inteiro.

Então, digamos que originalmente foo retornou 5 e, mais tarde, em sua função principal, você diz foo() = 10;, em seguida, imprime foo, ele imprimirá 10 em vez de 5.

Espero que faça sentido :)

Eu também sou novo em programação. É interessante ver perguntas como essa que te fazem pensar! :)

psj01
fonte