c++ templates c++11 variadic-templates

C++ 11 variadic std:: parámetro de función



templates c++11 (4)

Una función llamada test toma std :: function <> como parámetro.

template<typename R, typename ...A> void test(std::function<R(A...)> f) { // ... }

Pero, si hago lo siguiente:

void foo(int n) { /* ... */ } // ... test(foo);

El compilador (gcc 4.6.1) dice que no matching function for call to test(void (&)(int)) .

Para hacer que la última línea de test(foo) compile y funcione correctamente, ¿cómo puedo modificar la función de test() ? En la función test() , necesito f con el tipo de std :: function <>.

Quiero decir, ¿hay algún truco de plantilla para que el compilador determine la firma de la función ( foo en el ejemplo) y la convierta a std::function<void(int)> automáticamente?

EDITAR

También quiero que esto funcione para las lambdas (declaradas y sin estado).


Este es uno antiguo, y parece que no puedo encontrar mucho sobre el mismo tema, así que pensé en seguir adelante y escribir una nota.

Compilado en GCC 4.8.2, los siguientes trabajos:

template<typename R, typename... A> R test(const std::function<R(A...)>& func) { // ... }

Sin embargo, no puede simplemente llamarlo pasando sus punteros, lambdas, etc. Sin embargo, los siguientes 2 ejemplos funcionan con él:

test(std::function<void(int, float, std::string)>( [](int i, float f, std::string s) { std::cout << i << " " << f << " " << s << std::endl; }));

También:

void test2(int i, float f, std::string s) { std::cout << i << " " << f << " " << s << std::endl; } // In a function somewhere: test(std::function<void(int, float, std::string)>(&test2));

La desventaja de estos debe sobresalir bastante: tienes que declarar explícitamente la función std :: para ellos, lo que podría parecer un poco feo.

Dicho esto, sin embargo, lo combiné con una tupla que se expande para llamar a la función entrante, y funciona, solo requiere un poco más de un dicho explícito de lo que está haciendo llamando a la función de prueba.

Código de ejemplo que incluye la tupla, si quieres jugar con él: http://ideone.com/33mqZA


Parece que quieres usar la sobrecarga

template<typename R, typename ...A> void test(R f(A...)) { test(std::function<R(A...)>(f)); }

Esta implementación simple aceptará la mayoría, si no todas las funciones que intentará pasar. Las funciones exóticas serán rechazadas (como void(int...) ). Más trabajo te dará más generosidad.


Por lo general, no es aconsejable aceptar std::function por valor a menos que esté en "delimitación binaria" (por ejemplo, biblioteca dinámica, API "opaca") ya que, como acaba de ver, causan estragos en la sobrecarga. Cuando una función toma de hecho una std::function por valor, a menudo corresponde a la persona que llama construir el objeto para evitar los problemas de sobrecarga (si la función está sobrecargada).

Como sin embargo, ha escrito una plantilla, es probable que no esté utilizando std::function (como tipo de parámetro) para los beneficios del borrado de tipo. Si lo que quiere hacer es inspeccionar functores arbitrarios, necesita algunos rasgos para eso. Por ejemplo, Boost.FunctionTypes tiene rasgos como result_type y parameter_types . Un ejemplo mínimo, funcional:

#include <functional> #include <boost/function_types/result_type.hpp> #include <boost/function_types/parameter_types.hpp> #include <boost/function_types/function_type.hpp> template<typename Functor> void test(Functor functor) // accept arbitrary functor! { namespace ft = boost::function_types; typedef typename ft::result_type<Functor>::type result_type; typedef ft::parameter_types<Functor> parameter_types; typedef typename boost::mpl::push_front< parameter_types , result_type >::type sequence_type; // sequence_type is now a Boost.MPL sequence in the style of // mpl::vector<int, double, long> if the signature of the // analyzed functor were int(double, long) // We now build a function type out of the MPL sequence typedef typename ft::function_type<sequence_type>::type function_type; std::function<function_type> function = std::move(functor); }

Como nota final, no recomiendo los funtores de introspección (es decir, la búsqueda de su tipo de resultado y el tipo de argumento) en el caso general, ya que simplemente no funciona para los funtores polimórficos. Considere varios operator() sobrecargados operator() : entonces no hay ningún tipo de resultado o tipo de argumento ''canónico''. Con C ++ 11 es mejor aceptar "con entusiasmo" cualquier tipo de functor, o restringirlo usando técnicas como SFINAE o static_assert según las necesidades, y más adelante (cuando haya parámetros disponibles) para usar std::result_of para inspeccionar el resultado escriba para un conjunto dado de argumentos . Un caso en el que es deseable restringir por adelantado es cuando el objetivo es almacenar funtores en, por ejemplo, un contenedor de std::function<Sig> .

Para obtener una idea de lo que quiero decir con el párrafo anterior, es suficiente probar el fragmento anterior con los funtores polimórficos.


std::function implementa la interfaz invocable, es decir, se parece a una función, pero eso no significa que deba exigir que los objetos que se pueden std::function sean std::function s.

template< typename F > // accept any type void test(F const &f) { typedef std::result_of< F( args ) >::type R; // inspect with traits queries }

Escribir pato es la mejor política en metaprogramación de plantillas. Al aceptar un argumento de plantilla, no sea específico y simplemente deje que el cliente implemente la interfaz.

Si realmente necesita una std::function para volver a apuntar a la variable o algo así, y sabe que la entrada es un puntero de función en bruto, puede descomponer un tipo de puntero de función en bruto y reconsiderarlo en un std::function

template< typename R, typename ... A > void test( R (*f)( A ... ) ) { std::function< R( A ... ) > internal( f ); }

Ahora el usuario no puede pasar una std::function porque se ha encapsulado dentro de la función. Puede mantener su código existente como otra sobrecarga y simplemente delegar a eso, pero tenga cuidado de mantener las interfaces simples.

En cuanto a las lambdas con estado, no sé cómo manejar ese caso. No se descomponen en punteros de función y, por lo que sé, los tipos de argumentos no se pueden consultar ni deducir. Esta información es necesaria para instanciar la std::function , para bien o para mal.