Estou tentando fornecer um invólucro std::invoke
para deduzir o tipo de função mesmo quando a função está sobrecarregada.
(Fiz uma pergunta relacionada ontem para a versão do ponteiro variável e do método).
Quando a função possui um argumento, esse código (C ++ 17) funciona conforme o esperado em condições normais de sobrecarga:
#include <functional>
template <typename ReturnType, typename ... Args>
using FunctionType = ReturnType (*)(Args...);
template <typename S, typename T>
auto Invoke (FunctionType<S, T> func, T arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&> func, T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, const T&> func, const T & arg)
{
return std::invoke(func, arg);
}
template <typename S, typename T>
auto Invoke (FunctionType<S, T&&> func, T && arg)
{
return std::invoke(func, std::move(arg));
}
Reduzir o inchaço do código é obviamente necessário para mais argumentos de entrada, mas esse é um problema separado.
Se o usuário tiver sobrecargas diferentes apenas de const / reference, da seguinte forma:
#include <iostream>
void Foo (int &)
{
std::cout << "(int &)" << std::endl;
}
void Foo (const int &)
{
std::cout << "(const int &)" << std::endl;
}
void Foo (int &&)
{
std::cout << "(int &&)" << std::endl;
}
int main()
{
int num;
Foo(num);
Invoke(&Foo, num);
std::cout << std::endl;
Foo(0);
Invoke(&Foo, 0);
}
Em seguida, Invoke
deduz a função incorretamente, com saída g ++:
(int &)
(const & int)(int &&)
(const & int)
E clang ++:
(int &)
(const & int)(int &&)
(int &&)
(Obrigado a Geza por apontar que os resultados do clang eram diferentes).
Então, Invoke
tem um comportamento indefinido.
Suspeito que a metaprogramação seja a maneira de abordar esse problema. Independentemente disso, é possível lidar com a dedução de tipo corretamente no Invoke
site?
(int &&)
duas vezes no segundo caso.S
dedução de argumento. Tente comentar aconst T &
versãoInvoke
e observe o erro. Além disso, se o argumento for fornecido explicitamente (Invoke<void>(&Foo, num)
), a versão correta será chamada.Invoke
, ele pode instancia-lo com o const e o não-constFoo
. E não verifica se o tipo de retorno (S
) é o mesmo para os dois, portanto diz que não pode deduzirS
. Portanto, ele ignora este modelo. Embora a instanciação da constInvoke
possa ser feita apenas com a constFoo
, é possível deduzirS
nesse caso. Portanto, o compilador usa esse modelo.Respostas:
Teoria
Para cada modelo de função
Invoke
, a dedução do argumento do modelo (que deve ter êxito na resolução da sobrecarga para considerá-lo) considera cada umFoo
para ver se é possível deduzir no entanto muitos parâmetros do modelo (aqui, dois) para o parâmetro de uma função (func
) envolvido. A dedução geral pode ser bem-sucedida apenas se exatamenteFoo
corresponder (porque, caso contrário, não há como deduzirS
). (Isso foi mais ou menos declarado nos comentários.)O primeiro ("por valor")
Invoke
nunca sobrevive: pode deduzir de qualquer um dosFoo
s. Da mesma forma, a segundaconst
sobrecarga (“não referência”) aceita os dois primeirosFoo
s. Observe que eles se aplicam independentemente do outro argumento paraInvoke
(paraarg
)!A terceira
const T&
sobrecarga ( ) seleciona aFoo
sobrecarga correspondente e deduzT
=int
; o último faz o mesmo com a última sobrecarga (ondeT&&
é uma referência normal de valor) e, portanto, rejeita argumentos de valor apesar de sua referência universal (que deduzT
comoint&
(ouconst int&
) nesse caso e entra em conflito comfunc
a dedução de).Compiladores
Se o argumento for
arg
for um rvalue (e, como sempre, não for const), ambas asInvoke
sobrecargas plausíveis terão êxito na dedução e aT&&
sobrecarga deverá vencer (porque vincula uma referência de rvalue a um rvalue ).Para o caso dos comentários:
Nenhuma dedução ocorre
&Bar
desde que um modelo de função está envolvido; portanto,T
é deduzido com êxito (asint
) em todos os casos. Então, a dedução acontece novamente para cada caso para identificar aBar
especialização (se houver) para uso, deduzindoU
como falhar ,int&
,const int&
, eint&
, respectivamente. Osint&
casos são idênticos e claramente melhores, portanto a ligação é ambígua.Então Clang está bem aqui. (Mas não há "comportamento indefinido" aqui.)
Solução
Eu não tenho uma resposta geral para você; como certos tipos de parâmetros podem aceitar vários pares de categoria de valor / qualificação de const, não será fácil emular corretamente a resolução de sobrecarga em todos esses casos. Houve propostas para reificar conjuntos de sobrecarga de uma maneira ou de outra; você pode considerar uma das técnicas atuais nesse sentido (como um lambda genérico por nome da função de destino).
fonte