Há um debate sobre se esse é ou não um "comportamento intuitivo" nos comentários, então pensei em dar uma facada no raciocínio por trás desse comportamento.
Há uma palestra bastante agradável que foi dada no CPPCON que torna isso um pouco mais claro para mim { conversa , slides }. Basicamente, o que implica uma função que usa uma referência não-const? Que o objeto de entrada deve ser de leitura / gravação . Ainda mais forte, implica que pretendo modificar esse objeto, essa função tem efeitos colaterais . Um const ref implica somente leitura , e rvalue ref significa que eu posso pegar os recursos . Se você test_1()
acabar chamando o NON-CONST
construtor, isso significa que pretendo modificar esse objeto, mesmo depois de concluído, ele não existe mais,que (acho) seria um bug (estou pensando em um caso em que uma referência é vinculada durante a inicialização depende se o argumento passado é const ou não).
O que é um pouco mais preocupante para mim é a sutileza introduzida por test_2()
. Aqui, a inicialização da lista de cópias está ocorrendo em vez das regras relacionadas a [class.copy.elision] citadas acima. Agora você está realmente dizendo para retornar um objeto do tipo MyClass como se eu o tivesse inicializado buf
, para que o NON-CONST
comportamento seja invocado. Eu sempre pensei nas listas init como maneiras de ser mais concisas, mas aqui os aparelhos fazem uma diferença semântica significativa. Isso importaria mais se os construtores tivessem MyClass
um grande número de argumentos. Então, diga que você deseja criar buf
, modifique-o e retorne-o com o grande número de argumentos, invocando o CONST
comportamento. Por exemplo, digamos que você tenha os construtores:
template <size_t N>
MyClass(const char (&value)[N], int)
{
std::cout << "CONST int " << value << '\n';
}
template <size_t N>
MyClass(char (&value)[N], int)
{
std::cout << "NON-CONST int " << value << '\n';
}
E teste:
MyClass test_0() {
char buf[30] = "test_0";
return {buf,0};
}
Godbolt nos diz que obtemos NON-CONST
comportamento, embora CONST
seja provavelmente o que queremos (depois de você ter bebido o auxílio legal na semântica de argumentos de função). Mas agora a inicialização da lista de cópias não faz o que gostaríamos. O teste a seguir melhora meu argumento:
MyClass test_0() {
char buf[30] = "test_0";
buf[0] = 'T';
const char (&bufR)[30]{buf};
return {bufR,0};
}
// OUTPUT: CONST int Test_0
Agora, para obter a semântica adequada com a inicialização da lista de cópias, o buffer precisa ser "recuperado" no final. Eu acho que se o objetivo fosse que esse objeto inicializasse outro MyClass
objeto, apenas o uso do NON-CONST
comportamento na lista de cópias de retorno seria bom se o construtor de movimentação / cópia invocasse qualquer que seja o comportamento apropriado, mas isso está começando a parecer bastante delicado.