c++ - template - ¿Cómo almacenar argumentos de plantilla variadic?
c++ variadic template (3)
¿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 adelante:
void main(){
Action<int,int> add([](int x, int y){std::cout << (x+y);}, 3, 4);
//...
add.act();
}
Creo que tienes un problema XY. ¿Por qué tomarse tantas molestias para almacenar el paquete de parámetros cuando podría usar una lambda en el callsite? 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();
}
Para lograr lo que quiere hacer aquí, tendrá que almacenar sus argumentos de plantilla en una tupla:
std::tuple<Ts...> args;
Además, tendrás que cambiar un poco tu constructor. En particular, inicializar args
con std::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 secuencia muy parecido a esto:
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 dicho 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! Entonces 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 en 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();
}
Puede usar 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;
}
Observe que std::bind
es una función y necesita almacenar, como miembro de datos, el resultado de llamarlo. 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 de decltype
y std::declval
para calcular ese tipo de datos en tiempo de compilación. Consulte la definición de Action::bind_type
anterior.
También observe cómo utilicé referencias universales en el constructor con plantilla. Esto asegura que puede pasar argumentos que no coinciden con los parámetros de la plantilla de clase T...
exactamente (por ejemplo, puede usar referencias rvalue a algunas de las T
y las enviará tal como están a la llamada de bind
).
Nota final: si desea almacenar argumentos como referencias (para que la función que aprueba pueda modificar, en lugar de simplemente usarlos), necesita usar std::ref
para envolverlos en objetos de referencia. Simplemente al pasar un T &
creará una copia del valor, no una referencia.