Можно ли как-нибудь сохранить пакет параметров для последующего использования?
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
}
}
Потом позже:
void main(){
Action<int,int> add([](int x, int y){std::cout << (x+y);}, 3, 4);
//...
add.act();
}
c++
c++11
variadic-templates
Эрик Б.
источник
источник
Ответы:
Чтобы выполнить то, что вы хотите здесь, вам нужно будет сохранить аргументы шаблона в кортеже:
std::tuple<Ts...> args;
Кроме того, вам придется немного изменить свой конструктор. В частности, инициализация
args
с помощью,std::make_tuple
а также разрешение универсальных ссылок в вашем списке параметров:template <typename F, typename... Args> Action(F&& func, Args&&... args) : f(std::forward<F>(func)), args(std::forward<Args>(args)...) {}
Более того, вам нужно будет настроить генератор последовательности примерно так:
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...> {}; }
И вы можете реализовать свой метод в терминах одного взятия такого генератора:
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); }
И вот оно! Итак, теперь ваш класс должен выглядеть так:
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); } };
Вот ваша полная программа на Coliru.
Обновление: вот вспомогательный метод, для которого не требуется указывать аргументы шаблона:
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(); }
И снова вот еще одна демонстрация.
источник
void print(const std::string&); std::string hello(); auto act = make_action(print, hello());
не годится. Я бы предпочел поведениеstd::bind
, которое создает копию каждого аргумента, если вы не отключите это с помощьюstd::ref
илиstd::cref
.args(std::make_tuple(std::forward<Args>(args)...))
наargs(std::forward<Args>(args)...)
. Кстати, я написал это очень давно, и я бы не стал использовать этот код для привязки функции к некоторым аргументам. Я бы просто использовалstd::invoke()
илиstd::apply()
сейчас.Вы можете использовать
std::bind(f,args...)
для этого. Он сгенерирует перемещаемый и, возможно, копируемый объект, который хранит копию объекта функции и каждого из аргументов для дальнейшего использования:#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; }
Обратите внимание, что
std::bind
это функция, и вам необходимо сохранить как член данных результат ее вызова. Тип данных этого результата нелегко предсказать (Стандарт даже не определяет его точно), поэтому я использую комбинациюdecltype
иstd::declval
для вычисления этого типа данных во время компиляции. См. ОпределениеAction::bind_type
выше.Также обратите внимание, как я использовал универсальные ссылки в шаблонном конструкторе. Это гарантирует, что вы можете передавать аргументы, которые не соответствуют
T...
точно параметрам шаблона класса (например, вы можете использовать ссылки rvalue на некоторые из них,T
и вы получите их, как есть, дляbind
вызова).Заключительное примечание: если вы хотите сохранить аргументы в виде ссылок (чтобы передаваемая функция могла их изменять, а не просто использовать), вам необходимо использовать их
std::ref
для обертывания в ссылочные объекты. Простая передачаT &
создаст копию значения, а не ссылку.Операционный код на Coliru
источник
add
определены в другой области, а не гдеact()
вызывается? Разве конструктор не должен получитьConstrT&... args
вместоConstrT&&... args
?bind()
? Посколькуbind()
гарантировано создание копий (или переход к недавно созданным объектам), я не думаю, что это может быть проблемой.bind_(f, std::forward<ConstrT>(args)...)
это неопределенное поведение в соответствии со стандартом, поскольку этот конструктор определяется реализацией.bind_type
указано, что его можно копировать и / или перемещать, поэтому онbind_{std::bind(f, std::forward<ConstrT>(args)...)}
должен работать.Этот вопрос был из C ++ 11 дней. Но для тех, кто нашел его в результатах поиска, несколько обновлений:
std::tuple
Член по - прежнему простой способ хранить аргументы вообще. (std::bind
Решение, подобное @ jogojapan, также будет работать, если вы просто хотите вызвать определенную функцию, но не если вы хотите получить доступ к аргументам другими способами или передать аргументы более чем одной функции и т. Д.)В C ++ 14 и более поздних версиях
std::make_index_sequence<N>
илиstd::index_sequence_for<Pack...>
можно заменитьhelper::gen_seq<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...>{}); } // ... };
В C ++ 17 и более поздних версиях
std::apply
может использоваться для распаковки кортежа:template <typename... Ts> class Action { // ... void act() { std::apply(f, args); } };
Вот полная программа на C ++ 17, демонстрирующая упрощенную реализацию. Я также обновил,
make_action
чтобы избежать ссылочных типов вtuple
, что всегда было плохо для аргументов rvalue и довольно рискованно для аргументов lvalue.источник
Я думаю, у вас проблема XY. Зачем тратить силы на сохранение пакета параметров, если можно просто использовать лямбда-выражение на сайте вызова? т.е.
#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(); }
источник