Esta é uma continuação desta pergunta: Por que posso chamar um callable que é referenciado por constante e onde o callable real é um lambda mutável?
Posso imitar a conversão implícita de um fechamento mutável const para um ponteiro de função quando ele é chamado assim:
#include <iostream>
void dummy() { std::cout << "call dummy\n";}
using fptr = void(*)();
struct callable {
void operator()() {std::cout << "call callable\n"; }
operator fptr() const {
std::cout << "convert\n";
return &dummy;
}
};
int main()
{
auto exec = []( auto const &fn ) {
std::cout << "call\n";
fn();
};
exec( callable{} );
}
Não entendo por que a conversão implícita é acionada. Ela não ocorre ao passar o argumento (o argumento é deduzido, portanto, não leva em consideração a conversão implícita), mas apenas quando ()
é invocada, como evidenciado pela saída:
call
convert
call dummy
Tentei experimentar para entender o que estava acontecendo. Tentar algo semelhante com um operador diferente não funcionou (como esperado):
#include <iostream>
struct bar {
void operator+(int) const { std::cout << "bar\n"; };
};
struct foo {
void operator+(int) { std::cout << "foo\n"; }
operator bar() const { return {}; }
};
int main() {
auto exec = []( auto const &fn ) {
std::cout << "call\n";
fn + 42;
};
exec(foo{});
};
erro :
<source>:15:12: error: passing 'const foo' as 'this' argument discards qualifiers [-fpermissive]
15 | fn + 42;
| ~~~^~~~
O que há de especial no operador de chamada? Como é possível que ele fn()
converta implicitamente fn
para chamar outra coisa?
PS: Enquanto isso, descobri que deve estar relacionado à conversão para ponteiro de função, porque quando transformo o primeiro exemplo em algo mais parecido que o segundo, recebo o erro esperado:
#include <iostream>
struct foo {
void operator()() const { std::cout << "call foo\n"; }
};
struct callable {
void operator()() {std::cout << "call callable\n"; }
operator foo() const { return {}; };
};
int main()
{
auto exec = []( auto const &fn ) {
std::cout << "call\n";
fn();
};
exec( callable{} );
}
Agora recebo o erro esperado
<source>:17:11: error: no match for call to '(const callable) ()'
17 | fn();
| ~~^~
Agora a questão é: O que há de especial nas conversões para o tipo de ponteiro de função?
Parece que fn()
leva em conta conversões para ponteiros de função. Por quê?
Este comportamento especial para conversão em tipos de função é definido em [over.call.object] :
No seu exemplo, a expressão pós-fixada
E
éfn
, que é avaliada como um objeto de classe do tipoconst callable
. Comocallable
há um operador de conversão para "ponteiro para a função de()
retornovoid
" e esse operador éconst
qualificado como -, a função substitutavoid unique_name(fptr F) { return F(); }
é imaginada e a chamadaunique_name((fn))
é considerada para a expressãofn()
.Para realmente tornar o
+
caso equivalente, seria algo assim :E então ele compila e gera:
A diferença é que membro
bar::operator+(int)
não é um candidato, enquanto não membro::operator+(bar const&, int)
é (já que está no escopo; não será encontrado via ADL se você envolvê-lo em um namespace - tipos de retorno de operador de conversão não contribuem para o ADL dessa forma).Na verdade, inteiros possuem operadores não-membros incorporados definidos, conforme descrito em [over.built/10] . O comportamento da expressão de chamada é definido separadamente em [over.call.object], mas é essencialmente o mesmo como se não-membro
::operator()(F*)
fosse uma coisa e definido para cada tipo de funçãoF
.