¿Es posible almacenar un paquete de parámetros de alguna manera para un uso posterior?
template <typename... T>
class Action {
private:
std::function<void(T...)> f;
T... args; // <--- something like this
public:
Action(std::function<void(T...)> f, T... args) : f(f), args(args) {}
void act(){
f(args); // <--- such that this will be possible
}
}
Luego, más tarde:
void main(){
Action<int,int> add([](int x, int y){std::cout << (x+y);}, 3, 4);
//...
add.act();
}
c++
c++11
variadic-templates
Eric B
fuente
fuente
Respuestas:
Para lograr lo que quiere que se haga aquí, tendrá que almacenar los argumentos de su plantilla en una tupla:
std::tuple<Ts...> args;
Además, tendrás que cambiar un poco tu constructor. En particular, inicializar
args
con unstd::make_tuple
y también permitir referencias universales en su lista de parámetros:template <typename F, typename... Args> Action(F&& func, Args&&... args) : f(std::forward<F>(func)), args(std::forward<Args>(args)...) {}
Además, tendrías que configurar un generador de secuencias como este:
namespace helper { template <int... Is> struct index {}; template <int N, int... Is> struct gen_seq : gen_seq<N - 1, N - 1, Is...> {}; template <int... Is> struct gen_seq<0, Is...> : index<Is...> {}; }
Y puede implementar su método en términos de uno que tome tal generador:
template <typename... Args, int... Is> void func(std::tuple<Args...>& tup, helper::index<Is...>) { f(std::get<Is>(tup)...); } template <typename... Args> void func(std::tuple<Args...>& tup) { func(tup, helper::gen_seq<sizeof...(Args)>{}); } void act() { func(args); }
¡Y eso es todo! Así que ahora tu clase debería verse así:
template <typename... Ts> class Action { private: std::function<void (Ts...)> f; std::tuple<Ts...> args; public: template <typename F, typename... Args> Action(F&& func, Args&&... args) : f(std::forward<F>(func)), args(std::forward<Args>(args)...) {} template <typename... Args, int... Is> void func(std::tuple<Args...>& tup, helper::index<Is...>) { f(std::get<Is>(tup)...); } template <typename... Args> void func(std::tuple<Args...>& tup) { func(tup, helper::gen_seq<sizeof...(Args)>{}); } void act() { func(args); } };
Aquí está su programa completo sobre Coliru.
Actualización: aquí hay un método auxiliar por el cual la especificación de los argumentos de la plantilla no es necesaria:
template <typename F, typename... Args> Action<Args...> make_action(F&& f, Args&&... args) { return Action<Args...>(std::forward<F>(f), std::forward<Args>(args)...); } int main() { auto add = make_action([] (int a, int b) { std::cout << a + b; }, 2, 3); add.act(); }
Y nuevamente, aquí hay otra demostración.
fuente
void print(const std::string&); std::string hello(); auto act = make_action(print, hello());
no es bueno. Preferiría el comportamiento destd::bind
, que hace una copia de cada argumento a menos que lo desactive constd::ref
ostd::cref
.args(std::make_tuple(std::forward<Args>(args)...))
aargs(std::forward<Args>(args)...)
. Por cierto, escribí esto hace mucho tiempo y no usaría este código con el propósito de vincular una función a algunos argumentos. Solo usaríastd::invoke()
ostd::apply()
hoy en día.Puede utilizar
std::bind(f,args...)
para esto. Generará un objeto movible y posiblemente copiable que almacena una copia del objeto de función y de cada uno de los argumentos para su uso posterior:#include <iostream> #include <utility> #include <functional> template <typename... T> class Action { public: using bind_type = decltype(std::bind(std::declval<std::function<void(T...)>>(),std::declval<T>()...)); template <typename... ConstrT> Action(std::function<void(T...)> f, ConstrT&&... args) : bind_(f,std::forward<ConstrT>(args)...) { } void act() { bind_(); } private: bind_type bind_; }; int main() { Action<int,int> add([](int x, int y) { std::cout << (x+y) << std::endl; }, 3, 4); add.act(); return 0; }
Note que
std::bind
es una función y necesita almacenar, como miembro de datos, el resultado de llamarla. El tipo de datos de ese resultado no es fácil de predecir (el Estándar ni siquiera lo especifica con precisión), así que utilizo una combinación dedecltype
ystd::declval
para calcular ese tipo de datos en el momento de la compilación. Vea la definición deAction::bind_type
arriba.También observe cómo usé referencias universales en el constructor con plantilla. Esto asegura que pueda pasar argumentos que no coincidan
T...
exactamente con los parámetros de la plantilla de clase (por ejemplo, puede usar referencias rvalue a algunos de losT
y los enviará como están a labind
llamada).Nota final: si desea almacenar argumentos como referencias (para que la función que pase pueda modificarlos, en lugar de simplemente usarlos), debe usarlos
std::ref
para envolverlos en objetos de referencia. Simplemente pasar unT &
creará una copia del valor, no una referencia.Código operativo en Coliru
fuente
add
se definen en un ámbito diferente al de dóndeact()
se llama? ¿No debería el constructor obtener enConstrT&... args
lugar deConstrT&&... args
?bind()
? Dado quebind()
está garantizado para hacer copias (o moverse a objetos recién creados), no creo que pueda haber un problema.bind_(f, std::forward<ConstrT>(args)...)
hay un comportamiento indefinido según el estándar, ya que ese constructor está definido por la implementación.bind_type
está especificado para ser copia y / o mover-construible, por lobind_{std::bind(f, std::forward<ConstrT>(args)...)}
que aún debería funcionar.Esta pregunta fue de C ++ 11 días. Pero para aquellos que lo encuentran en los resultados de búsqueda ahora, algunas actualizaciones:
Un
std::tuple
miembro sigue siendo la forma más sencilla de almacenar argumentos en general. (Unastd::bind
solución similar a la de @ jogojapan también funcionará si solo desea llamar a una función específica, pero no si desea acceder a los argumentos de otras formas, o pasar los argumentos a más de una función, etc.)En C ++ 14 y posterior,
std::make_index_sequence<N>
ostd::index_sequence_for<Pack...>
puede reemplazar lahelper::gen_seq<N>
herramienta que se ve en la solución 0x499602D2 :#include <utility> template <typename... Ts> class Action { // ... template <typename... Args, std::size_t... Is> void func(std::tuple<Args...>& tup, std::index_sequence<Is...>) { f(std::get<Is>(tup)...); } template <typename... Args> void func(std::tuple<Args...>& tup) { func(tup, std::index_sequence_for<Args...>{}); } // ... };
En C ++ 17 y posteriores,
std::apply
se puede utilizar para encargarse de desempaquetar la tupla:template <typename... Ts> class Action { // ... void act() { std::apply(f, args); } };
Aquí hay un programa completo de C ++ 17 que muestra la implementación simplificada. También actualicé
make_action
para evitar tipos de referencia entuple
, lo que siempre era malo para los argumentos rvalue y bastante arriesgado para los argumentos lvalue.fuente
Creo que tienes un problema XY. ¿Por qué tomarse la molestia de almacenar el paquete de parámetros cuando podría usar una lambda en el sitio de llamadas? es decir,
#include <functional> #include <iostream> typedef std::function<void()> Action; void callback(int n, const char* s) { std::cout << s << ": " << n << '\n'; } int main() { Action a{[]{callback(13, "foo");}}; a(); }
fuente