É possível armazenar um pacote de parâmetros de alguma forma para um 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
}
}
Então, mais 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
fonte
fonte
Respostas:
Para realizar o que deseja fazer aqui, você terá que armazenar os argumentos do seu modelo em uma tupla:
std::tuple<Ts...> args;
Além disso, você terá que mudar um pouco o seu construtor. Em particular, inicializando
args
com umstd::make_tuple
e também permitindo referências universais em sua lista de parâmetros:template <typename F, typename... Args> Action(F&& func, Args&&... args) : f(std::forward<F>(func)), args(std::forward<Args>(args)...) {}
Além disso, você teria que configurar um gerador de sequência muito parecido com 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...> {}; }
E você pode implementar seu método em termos de um gerador:
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); }
E é isso! Portanto, agora sua classe deve ser assim:
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); } };
Aqui está o seu programa completo no Coliru.
Atualização: aqui está um método auxiliar pelo qual a especificação dos argumentos do modelo não é necessária:
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(); }
E novamente, aqui está outra demonstração.
fonte
void print(const std::string&); std::string hello(); auto act = make_action(print, hello());
não é bom. Eu prefiro o comportamento destd::bind
, que faz uma cópia de cada argumento, a menos que você desative-o comstd::ref
oustd::cref
.args(std::make_tuple(std::forward<Args>(args)...))
paraargs(std::forward<Args>(args)...)
. BTW, eu escrevi isso há muito tempo e não usaria esse código com o propósito de vincular uma função a alguns argumentos. Eu usaria apenasstd::invoke()
oustd::apply()
hoje em dia.Você pode usar
std::bind(f,args...)
para isso. Ele irá gerar um objeto móvel e possivelmente copiável que armazena uma cópia do objeto de função e de cada um dos argumentos para 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
é uma função e você precisa armazenar, como membro de dados, o resultado de chamá-la. O tipo de dados desse resultado não é fácil de prever (o padrão nem mesmo especifica precisamente), então eu uso uma combinação dedecltype
estd::declval
para calcular esse tipo de dados em tempo de compilação. Veja a definiçãoAction::bind_type
acima.Observe também como usei referências universais no construtor de modelo. Isso garante que você possa passar argumentos que não correspondem
T...
exatamente aos parâmetros do modelo de classe (por exemplo, você pode usar referências rvalue para alguns dosT
e você os encaminhará como estão para abind
chamada.)Nota final: Se você deseja armazenar argumentos como referências (de forma que a função que você passa possa modificá-los, ao invés de apenas usá-los), você precisa usá
std::ref
-los para envolvê-los em objetos de referência. Apenas passar umT &
criará uma cópia do valor, não uma referência.Código operacional no Coliru
fonte
add
é definido em um escopo diferente de ondeact()
é chamado? O construtor não deveria obter emConstrT&... args
vez deConstrT&&... args
?bind()
? Comobind()
é garantido fazer cópias (ou mover para objetos recém-criados), não acho que haja um problema.bind_(f, std::forward<ConstrT>(args)...)
é um comportamento indefinido de acordo com o padrão, uma vez que esse construtor é definido pela implementação.bind_type
é especificado para ser copiado e / ou construtível por movimento, portantobind_{std::bind(f, std::forward<ConstrT>(args)...)}
, ainda deve funcionar.Esta pergunta era de C ++ 11 dias. Mas, para aqueles que o encontram nos resultados da pesquisa agora, algumas atualizações:
Um
std::tuple
membro ainda é a maneira direta de armazenar argumentos em geral. (Umastd::bind
solução semelhante à de @ jogojapan também funcionará se você quiser apenas chamar uma função específica, mas não se quiser acessar os argumentos de outras maneiras, ou passar os argumentos para mais de uma função, etc.)Em C ++ 14 e posterior,
std::make_index_sequence<N>
oustd::index_sequence_for<Pack...>
pode substituir ahelper::gen_seq<N>
ferramenta vista na solução 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...>{}); } // ... };
No C ++ 17 e posterior,
std::apply
pode ser usado para descompactar a tupla:template <typename... Ts> class Action { // ... void act() { std::apply(f, args); } };
Aqui está um programa C ++ 17 completo mostrando a implementação simplificada. Eu também atualizei
make_action
para evitar tipos de referência notuple
, o que sempre foi ruim para argumentos rvalue e bastante arriscado para argumentos lvalue.fonte
Acho que você tem um problema XY. Por que se dar ao trabalho de armazenar o pacote de parâmetros quando você poderia apenas usar um lambda no call center? ie,
#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(); }
fonte