Se alguém chama a função de membro do objeto explícito de um temporário, a movimentação do temporário deve ser omitida no parâmetro do objeto explícito?
Considere o exemplo a seguir, onde struct A
o construtor de movimentação foi excluído e f(this A)
é invocado para um objeto temporário A
:
struct A {
A() {}
A(A&&) = delete;
void f(this A) {}
};
int main() {
A{}.f();
}
O programa é aceito no GCC, mas tanto o Clang quanto o MSVC o rejeitam:
chamada para construtor excluído de 'A'
erro C2280: 'A::A(A &&)': tentando fazer referência a uma função excluída
Demonstração on-line: https://gcc.godbolt.org/z/rbv14cnz5
Qual compilador está correto aqui?
Este é CWG2813 . Como observa a descrição do problema, o texto em C++ 23 requer um glvalue para o operando esquerdo do operador ponto, o que implica que se o operando esquerdo for um pré-valor, ele deve ser convertido primeiro em um glvalue (materializado), o que evita copiar/mover elisão. Este resultado é indesejável; gostaríamos que o prvalue inicializasse o parâmetro do objeto explícito diretamente. Assim, foi feita uma alteração na redação e o mesmo foi aprovado como DR. Os compiladores levarão algum tempo para se atualizarem, mas a intenção é que o exemplo seja aceito.
O CGC está correto. Infelizmente, a norma não contém uma declaração explícita sobre esse fato. Em vez disso, é a ausência de palavras especiais para parâmetros de objetos explícitos em vários locais que torna o seu programa correto.
A expressão
A{}.f()
consiste na expressãoA{}
incorporada em uma expressão de acesso de membro de classeA{}.f
, que por sua vez está incorporada em uma expressão de chamada de funçãoA{}.f()
. Começando com o acesso do membro, não há nenhuma redação que[expr.ref]
exija que o operando receptor seja ou seja convertido em um glvalue neste caso. A cláusula 2 impõe esta restrição apenas para membros de dados não estáticos :A cláusula (7.3) que trata do acesso às funções apenas diz
Novamente, a parte importante é a falta de qualquer redação que restrinja
A{}
ser um glvalue.Passando para a resolução de sobrecarga, a regra relevante é
[over.call.func]/2
:Então, no seu caso,
A{}.f()
torna-sef(A{})
para fins de resolução de sobrecarga, enquanto a sobrecarga definida em questão é simplesmentef(this A)
. A resolução de sobrecarga deverá ser bem-sucedida. As mensagens de erro do Clang e do MSVC sugerem que eles chegaram até aqui com sucesso.Os parâmetros de objeto explícitos não são tratados especialmente pela regra que rege como os parâmetros de função são inicializados. Parâmetros de objeto implícitos são o caso especial.
[expr.call]/6
lê:Novamente, a sentença que faria seu código falhar (a última) não se aplica neste caso, pois
f
não é uma função de membro de objeto implícita.Finalmente, à medida que a chamada de função é avaliada, o
this A
parâmetro tof
precisa ser inicializado pela expressão prvalueA{}
. Isso é bem-sucedido, sem o envolvimento do construtor do movimento, por[dcl.init.general]/16.6
A{}
( Afinal, a expressão não usa o construtor move.)À parte, salientarei que a sua formulação da pergunta não é estritamente correta.
A{}
não é “temporário”; é um pré-valor. Um pré-valor é uma expressão que, quando avaliada, inicializa uma determinada parte da memória com um objeto. Quando uma funçãovoid g(A);
é chamada porg(A{})
, o fato de não haver movimentação não deve ser considerado como "elisão". Pensar em termos de "elisão" só faz sentido quando comparamos C++ 11 com C++ 03. Desde o C++ 11, simplesmente não há movimento para elidir. Sua pergunta é melhor escrita "quando uma função com um parâmetro de objeto explícito por valor é chamada em um pré-valor do tipo de classe, um valor temporário é materializado e movido?" A resposta que estou dando é "não, o prvalue inicializa diretamente o parâmetro e não há movimento temporário nem movimento".