Estoy tratando de proporcionar un contenedor std::invoke
para 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 Invoke
deduce 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 Invoke
tiene 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 Invoke
sitio?
(int &&)
dos veces para el segundo caso.S
deducción de argumentos. Intente comentar laconst T &
versión deInvoke
y 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 soloInvoke
se puede hacer con la constanteFoo
, por lo que se puede deducirS
en 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 unaFoo
para 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 siFoo
coincide 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")
Invoke
nunca sobrevive: se puede deducir de cualquiera de losFoo
s. Del mismo modo, la segundaconst
sobrecarga ("sin referencia") acepta los dos primerosFoo
s. Tenga en cuenta que estos se aplican independientemente del otro argumento paraInvoke
(forarg
)!La tercera
const T&
sobrecarga ( ) selecciona laFoo
sobrecarga 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 deduceT
comoint&
(oconst int&
) en ese caso y entra en conflicto confunc
la deducción).Compiladores
Si el argumento para
arg
es un valor r (y, como de costumbre, no es constante), ambasInvoke
sobrecargas 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,
&Bar
ya que se trata de una plantilla de función, por lo queT
se deduce con éxito (asint
) en todos los casos. Entonces, la deducción pasa de nuevo para cada caso para identificar laBar
especialización (si lo hay) para su uso, deduciendoU
como 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