Estou tentando projetar uma função simples como esta:
void draw(Callback callback)
{
Drawing drawing("name");
callback(drawing);
std::cout << drawing.name() << std::endl;
}
onde drawing
deve ser passado para o retorno de chamada como referência, para que o chamador possa modificá-lo da seguinte forma:
draw([](auto& drawing) {
drawing.set_name("another name");
});
Agora isso funciona, porque estou digitando explicitamente o parâmetro de retorno de chamada como auto&
(ou Drawing&
). Porém, quando apenas digito o parâmetro como auto
(or Drawing
), o código também compila, mas não funciona mais conforme o esperado. A drawing
instância é copiada em vez de passada por referência.
Gostaria que o código não fosse mais compilado quando o parâmetro lambda não fosse digitado explicitamente como referência.
Eu tentei o seguinte:
using Callback = std::function<void(Drawing&)>;
:drawing
não é modificado quando não é digitado explicitamente comoauto&
.template<typename T> concept Callback = std::is_invocable<void, T, Drawing&>;
: o mesmo que acima.using Callback = void (*)(Drawing&);
: isso realmente funciona, mas perco a capacidade de usar a captura.
Então, como faço para digitar Callback corretamente para que apenas Drawing&
seja um parâmetro válido para o lambda?
Você pode restringir
draw
para que não possa ser chamado com um rvalue-acceptingcallback
:Se o dado
callback
fosse aceitoauto
ouauto&&
, não satisfaria a restrição:Veja o exemplo ao vivo no Compiler Explorer .
Por que isso funciona
O único tipo de referência que não pode ser vinculado a rvalues é uma referência não-const lvalue. Ao restringir a função para que o retorno de chamada não possa aceitar um rvalue, desqualificamos os retornos de chamada que levam
Drawing
,Drawing&&
,const Drawing&
econst Drawing&&
, uma vez que todos eles podem ser vinculados a um rvalue.Essencialmente, forçamos o usuário a pegar
Drawing&
(ouauto&
), e a cópia acidental é impossível porque as referências lvalue não podem ser vinculadas a objetos temporários.Nota: as referências de rvalue são convertidas em xvalues do mesmo tipo antes de qualquer análise; é por isso que estou dizendo "rvalores" e não falando sobre referências nesses marcadores.
Conselhos adicionais
Observe que se o seu objetivo é
Drawing
não ser copiado acidentalmente, você pode evitar isso de maneira mais confiável, excluindo explicitamente o construtor de cópia e o operador de atribuição de cópia. No entanto, isso obviamente se aplicaria em todos os lugares, não apenas dentro dedraw
.