Quais são as implicações das garantias de ordem de avaliação votadas em C ++ 17 (P0145) no código C ++ típico?
O que isso muda em coisas como as seguintes?
i = 1;
f(i++, i)
e
std::cout << f() << f() << f();
ou
f(g(), h(), j());
c++
c++17
operator-precedence
Johan Lundberg
fonte
fonte
Respostas:
Alguns casos comuns em que a ordem de avaliação não foi especificada até o momento são especificados e válidos com
C++17
. Algum comportamento indefinido agora não é especificado.foi indefinido, mas agora não foi especificado. Especificamente, o que não é especificado é a ordem em que cada argumento para
f
é avaliado em relação aos outros.i++
pode ser avaliado antesi
, ou vice-versa. Na verdade, ele pode avaliar uma segunda chamada em uma ordem diferente, apesar de estar no mesmo compilador.No entanto, a avaliação de cada argumento é necessária para ser executada completamente, com todos os efeitos colaterais, antes da execução de qualquer outro argumento. Portanto, você pode obter
f(1, 1)
(segundo argumento avaliado primeiro) ouf(1, 2)
(primeiro argumento avaliado primeiro). Mas você nunca obteráf(2, 2)
ou qualquer outra coisa dessa natureza.não foi especificado, mas se tornará compatível com a precedência do operador, de modo que a primeira avaliação de
f
venha primeiro no fluxo (exemplos abaixo).ainda tem uma ordem de avaliação não especificada de g, h e j. Observe que para
getf()(g(),h(),j())
, as regras indicam quegetf()
será avaliado antesg, h, j
.Observe também o seguinte exemplo do texto da proposta:
O exemplo vem de The C ++ Programming Language , 4ª edição, Stroustrup, e costumava ser um comportamento não especificado, mas com C ++ 17 funcionará conforme o esperado. Ocorreram problemas semelhantes com funções retomáveis (
.then( . . . )
).Como outro exemplo, considere o seguinte:
#include <iostream> #include <string> #include <vector> #include <cassert> struct Speaker{ int i =0; Speaker(std::vector<std::string> words) :words(words) {} std::vector<std::string> words; std::string operator()(){ assert(words.size()>0); if(i==words.size()) i=0; // Pre-C++17 version: auto word = words[i] + (i+1==words.size()?"\n":","); ++i; return word; // Still not possible with C++17: // return words[i++] + (i==words.size()?"\n":","); } }; int main() { auto spk = Speaker{{"All", "Work", "and", "no", "play"}}; std::cout << spk() << spk() << spk() << spk() << spk() ; }
Com C ++ 14 e antes, podemos (e iremos) obter resultados como
play no,and,Work,All,
ao invés de
All,work,and,no,play
Observe que o acima está em vigor o mesmo que
(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;
Mesmo assim, antes do C ++ 17 não havia garantia de que as primeiras chamadas viriam primeiro no fluxo.
Referências: Da proposta aceita :
Editar nota: minha resposta original foi mal interpretada
a(b1, b2, b3)
. A ordem deb1
,b2
,b3
ainda é indeterminado. (obrigado @KABoissonneault, todos os comentaristas.)No entanto, (como aponta @Yakk out) e isto é importante: Mesmo quando
b1
,b2
,b3
são expressões não-triviais, cada um deles são completamente avaliadas e amarrado ao respectivo parâmetro de função antes que os outros são começou a ser avaliada. O padrão afirma assim:No entanto, uma dessas novas frases está faltando no rascunho do GitHub :
O exemplo está aí. Ele resolve problemas de décadas atrás ( conforme explicado por Herb Sutter ) com segurança excepcional, onde coisas como
f(std::unique_ptr<A> a, std::unique_ptr<B> b); f(get_raw_a(), get_raw_a());
vazaria se uma das chamadas
get_raw_a()
fosse lançada antes que o outro ponteiro bruto fosse vinculado ao seu parâmetro de ponteiro inteligente.Conforme apontado por TC, o exemplo é falho, pois a construção de unique_ptr a partir do ponteiro bruto é explícita, impedindo que ele seja compilado. *
Observe também esta pergunta clássica (marcada como C , não C ++ ):
ainda está indefinido.
fonte
a
, entãob
, entãoc
, entãod
" e, em seguidaa(b1, b2, b3)
, mostrando , sugerindo que todas asb
expressões não são necessariamente avaliadas em qualquer ordem (caso contrário, seriaa(b, c, d)
)a(b1()(), b2()())
pode encomendarb1()()
eb2()()
em qualquer ordem, mas não pode fazerb1()
, em seguida,b2()()
entãob1()()
: já não intercalam suas execuções podem. Em suma, "8. ORDEM DE AVALIAÇÃO ALTERNATIVA PARA CHAMADAS DE FUNÇÃO" fazia parte da alteração aprovada.f(i++, i)
estava indefinido. Agora não foi especificado. O exemplo da string de Stroustrup provavelmente não foi especificado, não foi definido. `f (get_raw_a (), get_raw_a ());` não compilará já que ounique_ptr
construtor relevante é explícito. Finalmente,x++ + ++x
é indefinido, ponto final.A intercalação é proibida em C ++ 17
No C ++ 14, o seguinte não era seguro:
void foo(std::unique_ptr<A>, std::unique_ptr<B>); foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));
Existem quatro operações que acontecem aqui durante a chamada de função
new A
unique_ptr<A>
construtornew B
unique_ptr<B>
construtorA ordenação destes não foi especificada e, portanto, uma ordenação perfeitamente válida é (1), (3), (2), (4). Se esta ordem foi selecionada e (3) joga, então a memória de (1) vaza - nós não rodamos (2) ainda, o que teria evitado o vazamento.
No C ++ 17, as novas regras proíbem a intercalação. De [intro.execution]:
Há uma nota de rodapé nessa frase que diz:
Isso nos deixa com duas ordens válidas: (1), (2), (3), (4) ou (3), (4), (1), (2). Não é especificado qual pedido é usado, mas ambos são seguros. Todos os pedidos onde (1) (3) ambos acontecem antes de (2) e (4) agora são proibidos.
fonte
Encontrei algumas notas sobre a ordem de avaliação da expressão:
Em P0145R3.Refining Expression Evaluation Order for Idiomático C ++ encontrei:
Mas não o encontrei no padrão, em vez disso, no padrão eu encontrei:
Portanto, comparei de acordo com o comportamento em três compiladores para 14 e 17 padrões. O código explorado é:
#include <iostream> struct A { A& addInt(int i) { std::cout << "add int: " << i << "\n"; return *this; } A& addFloat(float i) { std::cout << "add float: " << i << "\n"; return *this; } }; int computeInt() { std::cout << "compute int\n"; return 0; } float computeFloat() { std::cout << "compute float\n"; return 1.0f; } void compute(float, int) { std::cout << "compute\n"; } int main() { A a; a.addFloat(computeFloat()).addInt(computeInt()); std::cout << "Function call:\n"; compute(computeFloat(), computeInt()); }
Resultados (quanto mais consistente é o clang):
<style type="text/css"> .tg { border-collapse: collapse; border-spacing: 0; border-color: #aaa; } .tg td { font-family: Arial, sans-serif; font-size: 14px; padding: 10px 5px; border-style: solid; border-width: 1px; overflow: hidden; word-break: normal; border-color: #aaa; color: #333; background-color: #fff; } .tg th { font-family: Arial, sans-serif; font-size: 14px; font-weight: normal; padding: 10px 5px; border-style: solid; border-width: 1px; overflow: hidden; word-break: normal; border-color: #aaa; color: #fff; background-color: #f38630; } .tg .tg-0pky { border-color: inherit; text-align: left; vertical-align: top } .tg .tg-fymr { font-weight: bold; border-color: inherit; text-align: left; vertical-align: top } </style> <table class="tg"> <tr> <th class="tg-0pky"></th> <th class="tg-fymr">C++14</th> <th class="tg-fymr">C++17</th> </tr> <tr> <td class="tg-fymr"><br>gcc 9.0.1<br></td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> </tr> <tr> <td class="tg-fymr">clang 9</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td> </tr> <tr> <td class="tg-fymr">msvs 2017</td> <td class="tg-0pky">compute int<br>compute float<br>add float: 1<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> </tr> </table>
fonte