Estoy tratando de proporcionar un contenedor std::invokepara hacer el trabajo de deducir el tipo de función incluso cuando la función está sobrecargada.
(Hice una pregunta relacionada ayer para la versión de puntero variadic y método).
Cuando la función tiene un argumento, este código (C ++ 17) funciona como se espera en condiciones normales 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));
}
Obviamente, es necesario reducir la acumulación de código para obtener más argumentos de entrada, pero ese es un problema separado.
Si el usuario tiene sobrecargas que difieren solo por constantes / referencias, así:
#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);
}
Luego Invokededuce la función incorrectamente, con salida de g ++:
(int &)
(const int &)(int &&)
(const int &)
Y clang ++:
(int &)
(const int &)(int &&)
(int &&)
(Gracias a geza por señalar que los resultados de clang fueron diferentes).
Entonces Invoketiene un comportamiento indefinido.
Sospecho que la metaprogramación sería la forma de abordar este problema. De todos modos, ¿es posible manejar la deducción de tipo correctamente en el Invokesitio?

(int &&)dos veces para el segundo caso.Sdeducción de argumentos. Intente comentar laconst T &versión deInvokey observe el error. Además, si el argumento se proporciona explícitamente (Invoke<void>(&Foo, num)), se llama a la versión correcta.Invoke, puede instanciarla tanto con constante como sin constanteFoo. Y no comprueba que el tipo de retorno (S) sea el mismo para ambos, por lo que dice que no puede deducirS. Entonces ignora esta plantilla. Si bien la creación de instancias de la constante soloInvokese puede hacer con la constanteFoo, por lo que se puede deducirSen este caso. Por lo tanto, el compilador usa esta plantilla.Respuestas:
Teoría
Para cada plantilla de función
Invoke, la deducción de argumento de plantilla (que debe tener éxito para que la resolución de sobrecarga lo considere) considera cada unaFoopara ver si puede deducir cuántos parámetros de plantilla (aquí, dos) para el parámetro de una función (func) involucrado. La deducción general solo puede tener éxito siFoocoincide exactamente uno (porque de lo contrario no hay forma de deducirloS). (Esto fue más o menos declarado en los comentarios).El primero ("por valor")
Invokenunca sobrevive: se puede deducir de cualquiera de losFoos. Del mismo modo, la segundaconstsobrecarga ("sin referencia") acepta los dos primerosFoos. Tenga en cuenta que estos se aplican independientemente del otro argumento paraInvoke(forarg)!La tercera
const T&sobrecarga ( ) selecciona laFoosobrecarga correspondiente y deduceT=int; el último hace lo mismo con la última sobrecarga (dondeT&&es una referencia de valor normal), y por lo tanto rechaza los argumentos de valor a pesar de su referencia universal (que deduceTcomoint&(oconst int&) en ese caso y entra en conflicto confuncla deducción).Compiladores
Si el argumento para
arges un valor r (y, como de costumbre, no es constante), ambasInvokesobrecargas plausibles tienen éxito en la deducción, y laT&&sobrecarga debería ganar (porque vincula una referencia de valor r a un valor r ).Para el caso de los comentarios:
No se realiza ninguna deducción,
&Barya que se trata de una plantilla de función, por lo queTse deduce con éxito (asint) en todos los casos. Entonces, la deducción pasa de nuevo para cada caso para identificar laBarespecialización (si lo hay) para su uso, deduciendoUcomo fallar ,int&,const int&, yint&respectivamente. Losint&casos son idénticos y claramente mejores, por lo que la llamada es ambigua.Entonces Clang está aquí. (Pero aquí no hay un "comportamiento indefinido").
Solución
No tengo una respuesta general para ti; Dado que ciertos tipos de parámetros pueden aceptar múltiples pares de categoría de valor / calificación constante, no será fácil emular la resolución de sobrecarga correctamente en todos estos casos. Ha habido propuestas para reificar conjuntos de sobrecarga de una forma u otra; puede considerar una de las técnicas actuales en ese sentido (como una lambda genérica por nombre de función de destino).
fuente