Czy można w jakiś sposób zapisać pakiet parametrów do późniejszego wykorzystania?
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
}
}
Później:
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.
źródło
źródło
Odpowiedzi:
Aby osiągnąć to, co chcesz tutaj zrobić, musisz przechowywać argumenty szablonu w krotce:
std::tuple<Ts...> args;
Ponadto będziesz musiał nieco zmienić swojego konstruktora. W szczególności, inicjalizacja
args
za pomocą,std::make_tuple
a także zezwolenie na uniwersalne odniesienia na liście parametrów:template <typename F, typename... Args> Action(F&& func, Args&&... args) : f(std::forward<F>(func)), args(std::forward<Args>(args)...) {}
Co więcej, musiałbyś skonfigurować generator sekwencji podobny do tego:
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...> {}; }
I możesz zaimplementować swoją metodę w ten sposób, że bierze taki generator:
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); }
I to wszystko! Więc teraz twoja klasa powinna wyglądać tak:
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); } };
Oto Twój pełny program na Coliru.
Aktualizacja: Oto metoda pomocnicza, dzięki której specyfikacja argumentów szablonu nie jest konieczna:
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(); }
I znowu, oto kolejne demo.
źródło
void print(const std::string&); std::string hello(); auto act = make_action(print, hello());
nie jest dobrze. Wolałbym zachowanie poleceniastd::bind
, które tworzy kopię każdego argumentu, chyba że wyłączysz go za pomocąstd::ref
lubstd::cref
.args(std::make_tuple(std::forward<Args>(args)...))
naargs(std::forward<Args>(args)...)
. Swoją drogą napisałem to dawno temu i nie użyłbym tego kodu do wiązania funkcji z jakimiś argumentami. Po prostu użyłbymstd::invoke()
lubstd::apply()
obecnie.Możesz użyć
std::bind(f,args...)
do tego. Wygeneruje ruchomy i prawdopodobnie kopiowalny obiekt, który przechowuje kopię obiektu funkcji i każdego z argumentów do późniejszego wykorzystania:#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; }
Zauważ, że
std::bind
jest to funkcja i jako element członkowski danych musisz zapisać wynik jej wywołania. Typ danych tego wyniku nie jest łatwy do przewidzenia (norma nawet nie określa go dokładnie), więc używam kombinacjidecltype
istd::declval
do obliczania tego typu danych w czasie kompilacji. Zobacz definicjęAction::bind_type
powyżej.Zwróć także uwagę, jak użyłem uniwersalnych odwołań w konstruktorze opartym na szablonach. Gwarantuje to, że możesz przekazywać argumenty, które nie pasują
T...
dokładnie do parametrów szablonu klasy (np. Możesz użyć odniesień rvalue do niektórych z argumentów,T
a otrzymasz je przekazane w niezmienionej postaci dobind
wywołania).Uwaga końcowa: jeśli chcesz przechowywać argumenty jako odwołania (aby funkcja, którą przekazujesz, mogła je modyfikować, a nie tylko ich używać), musisz użyć ich
std::ref
do zawijania ich w obiekty referencyjne. Samo przekazanie aT &
spowoduje utworzenie kopii wartości, a nie odniesienia.Kod operacyjny na Coliru
źródło
add
zostały zdefiniowane w innym zakresie niż miejsceact()
wywołania? Czy konstruktor nie powinien dostaćConstrT&... args
zamiastConstrT&&... args
?bind()
? Ponieważbind()
gwarantujemy wykonanie kopii (lub przeniesienie do świeżo utworzonych obiektów), nie sądzę, że może to być problem.bind_(f, std::forward<ConstrT>(args)...)
jest niezdefiniowane zachowanie zgodnie ze standardem, ponieważ ten konstruktor jest zdefiniowany w ramach implementacji.bind_type
jest określony jako konstruowalny do kopiowania i / lub przenoszenia, więcbind_{std::bind(f, std::forward<ConstrT>(args)...)}
nadal powinien działać.To pytanie pochodziło z C ++ 11 dni. Ale dla tych, którzy teraz znajdują go w wynikach wyszukiwania, kilka aktualizacji:
Członek
std::tuple
jest nadal prostym sposobem na ogólne przechowywanie argumentów. (std::bind
Rozwiązanie podobne do @ jogojapan zadziała również, jeśli chcesz po prostu wywołać określoną funkcję, ale nie, jeśli chcesz uzyskać dostęp do argumentów w inny sposób lub przekazać argumenty do więcej niż jednej funkcji itp.)W C ++ 14 i nowszych wersjach
std::make_index_sequence<N>
lubstd::index_sequence_for<Pack...>
może zastąpićhelper::gen_seq<N>
narzędzie widoczne w rozwiązaniu 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...>{}); } // ... };
W C ++ 17 i nowszych
std::apply
można zająć się rozpakowywaniem krotki:template <typename... Ts> class Action { // ... void act() { std::apply(f, args); } };
Oto pełny program C ++ 17 pokazujący uproszczoną implementację. Zaktualizowałem również,
make_action
aby uniknąć typów referencyjnych wtuple
, co zawsze było złe dla argumentów r-wartości i dość ryzykowne dla argumentów l-wartości.źródło
Myślę, że masz problem z XY. Po co zadawać sobie tyle trudu, aby przechowywać pakiet parametrów, skoro można po prostu użyć lambdy na stronie wywołania? to znaczy,
#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(); }
źródło