Qual é a resposta correta para cout << a ++ << a ;?

Recentemente, em uma entrevista, houve uma pergunta do tipo objective a seguir.

int a = 0; cout << a++ << a; 

Respostas:

uma. 10
b. 01
c. comportamento indefinido

Eu respondi a escolha b, ou seja, a saída seria “01”.

Mas para minha surpresa, mais tarde, um entrevistador me disse que a resposta correta é a opção c: indefinida.

Agora, eu sei o conceito de pontos de seqüência em C ++. O comportamento é indefinido para a seguinte instrução:

 int i = 0; i += i++ + i++; 

mas conforme meu entendimento para a instrução cout << a++ << a , o ostream.operator<<() seria chamado duas vezes, primeiro com ostream.operator<<(a++) e depois ostream.operator<<(a) .

Eu também verifiquei o resultado no compilador VS2010 e sua saída também é ’01’.

   

Você pode pensar em:

 cout < < a++ << a; 

Como:

 std::operator< <(std::operator<<(std::cout, a++), a); 

O C ++ garante que todos os efeitos colaterais de avaliações anteriores tenham sido executados em pontos de sequência . Não há pontos de sequência entre a avaliação de argumentos de function, o que significa que o argumento a pode ser avaliado antes do argumento std::operator< <(std::cout, a++) ou posterior. Então, o resultado do acima é indefinido.


Atualização do C ++ 17

Em C ++ 17, as regras foram atualizadas. Em particular:

Em uma expressão do operador de deslocamento E1< e E1>>E2 , cada cálculo de valor e efeito colateral de E1 é sequenciado antes de cada cálculo de valor e efeito colateral de E2 .

O que significa que requer que o código produza o resultado b , que produz 01 .

Consulte P0145R3 Ordem de Avaliação de Expressão de Refino para Idiomatic C ++ para obter mais detalhes.

Tecnicamente, no geral, isso é comportamento indefinido .

Mas há dois aspectos importantes para a resposta.

A declaração do código:

 std::cout < < a++ << a; 

é avaliado como:

 std::operator< <(std::operator<<(std::cout, a++), a); 

O padrão não define a ordem de avaliação de argumentos para uma function.
Então, quer:

  • std::operator< <(std::cout, a++) é avaliado primeiro ou
  • a é avaliado primeiro ou
  • pode ser qualquer ordem definida de implementação.

Este pedido não é especificado [Ref 1] conforme o padrão.

[Ref 1] C ++ 03 5.2.2 Chamada de function
Para 8

A ordem de avaliação dos argumentos não é especificada . Todos os efeitos colaterais das avaliações de expressões de argumentos entram em vigor antes que a function seja inserida. A ordem de avaliação da expressão postfix e da lista de expressões de argumentos não é especificada.

Além disso, não há um ponto de seqüência entre a avaliação de argumentos para uma function, mas um ponto de seqüência existe somente após a avaliação de todos os argumentos [Ref 2] .

[Ref 2] C ++ 03 1.9 Execução do programa [intro.execution]:
Parágrafo 17:

Ao chamar uma function (quer a function esteja inline ou não), existe um ponto de seqüência após a avaliação de todos os argumentos da function (se houver) que ocorre antes da execução de quaisquer expressões ou instruções no corpo da function.

Observe que, aqui, o valor de c está sendo acessado mais de uma vez sem um ponto de seqüência interveniente, em relação a isso, a norma diz:

[Ref 3] C ++ 03 5 Expressões [expr]:
Parágrafo 4:

....
Entre o ponto de seqüência anterior e o seguinte, um object escalar deve ter seu valor armazenado modificado no máximo uma vez pela avaliação de uma expressão. Além disso, o valor anterior deve ser acessado apenas para determinar o valor a ser armazenado . Os requisitos deste parágrafo devem ser atendidos para cada ordenação permitida das subexpressões de uma expressão completa; caso contrário, o comportamento é indefinido .

O código modifica c mais de uma vez sem intervir ponto de seqüência e não está sendo acessado para determinar o valor do object armazenado. Esta é uma violação clara da cláusula acima e, portanto, o resultado, conforme determinado pela norma, é o comportamento indefinido [Ref 3] .

Os pontos de sequência definem apenas uma ordenação parcial . No seu caso, você tem (uma vez que a resolução de sobrecarga é feita):

 std::cout.operator< <( a++ ).operator<<( a ); 

Existe um ponto de sequência entre o a++ e a primeira chamada para std::ostream::operator< < , e existe um ponto de sequência entre o segundo std::ostream::operator< < a segunda chamada para std::ostream::operator< < , mas há não é um ponto de sequência entre a++ e a ; as únicas restrições de pedido são que a++ seja totalmente avaliado (incluindo efeitos colaterais) antes da primeira chamada para o operator< < , e que o segundo seja totalmente avaliado antes da segunda chamada para o operator< < . (Existem também restrições causais de ordenação: a segunda chamada para o operator< < não pode preceder a primeira, pois requer os resultados da primeira como argumento). §5 / 4 (C ++ 03) declara:

Exceto onde indicado, a ordem de avaliação de operandos de operadores individuais e subexpressões de expressões individuais, e a ordem na qual os efeitos colaterais ocorrem, não é especificada. Entre o ponto de seqüência anterior e o seguinte, um object escalar deve ter seu valor armazenado modificado no máximo uma vez pela avaliação de uma expressão. Além disso, o valor anterior deve ser acessado apenas para determinar o valor a ser armazenado. Os requisitos deste parágrafo devem ser atendidos para cada ordenação permitida das subexpressões de uma expressão completa; caso contrário, o comportamento é indefinido.

Uma das ordenações permitidas da sua expressão é a++ , a primeira chamada para o operator< < , segunda chamada para o operator< < ; isso modifica o valor armazenado de a ( a++ ) e acessa-o diferente de determinar o novo valor (o segundo a ), o comportamento é indefinido.

A resposta correta é questionar a questão. A declaração é inaceitável porque o leitor não consegue ver uma resposta clara. Outra maneira de ver isso é que introduzimos efeitos colaterais (c ++) que tornam a declaração muito mais difícil de interpretar. O código conciso é ótimo, desde que seu significado seja claro.