Próbuję zapisać w std::tuple
zmiennej liczbie wartości, które później zostaną użyte jako argumenty wywołania wskaźnika funkcji, który pasuje do przechowywanych typów.
Stworzyłem uproszczony przykład pokazujący problem, który staram się rozwiązać:
#include <iostream>
#include <tuple>
void f(int a, double b, void* c) {
std::cout << a << ":" << b << ":" << c << std::endl;
}
template <typename ...Args>
struct save_it_for_later {
std::tuple<Args...> params;
void (*func)(Args...);
void delayed_dispatch() {
// How can I "unpack" params to call func?
func(std::get<0>(params), std::get<1>(params), std::get<2>(params));
// But I *really* don't want to write 20 versions of dispatch so I'd rather
// write something like:
func(params...); // Not legal
}
};
int main() {
int a=666;
double b = -1.234;
void *c = NULL;
save_it_for_later<int,double,void*> saved = {
std::tuple<int,double,void*>(a,b,c), f};
saved.delayed_dispatch();
}
Zwykle w przypadku problemów związanych z std::tuple
szablonami lub wariantami pisałbym inny szablon, taki jak template <typename Head, typename ...Tail>
rekurencyjna ocena wszystkich typów jeden po drugim, ale nie widzę sposobu na wywołanie wywołania funkcji.
Prawdziwa motywacja do tego jest nieco bardziej złożona i w większości to tylko ćwiczenie edukacyjne. Możesz założyć, że krotkę dostałem na podstawie umowy z innego interfejsu, więc nie można jej zmienić, ale chęć rozpakowania jej w wywołanie funkcji jest moja. Wyklucza to użycie std::bind
jako taniego sposobu na ominięcie podstawowego problemu.
Jaki jest czysty sposób wysłania wywołania przy użyciu std::tuple
lub alternatywny lepszy sposób osiągnięcia tego samego wyniku netto przechowywania / przekazywania niektórych wartości i wskaźnika funkcji do dowolnego punktu w przyszłości?
auto saved = std::bind(f, a, b, c);
... a potem po prostu zadzwonićsaved()
?Odpowiedzi:
Musisz zbudować pakiet parametrów liczb i rozpakować je
źródło
struct gens
ogólna definicja (ta, która dziedziczy po rozszerzonej pochodnej tego samego). Widzę, że ostatecznie osiąga specjalizację z 0. Jeśli nastrój ci odpowiada i masz zapasowe cykle, jeśli możesz rozwinąć tę kwestię i jak ją wykorzystasz, byłbym na zawsze wdzięczny. I żałuję, że nie mogłem głosować więcej niż sto razy. Miałem więcej zabawy grając ze styczną z tego kodu. Dzięki.seq<0, 1, .., N-1>
. Jak to działa:gens<5>: gens<4, 4>: gens<3, 3, 4>: gens<2, 2, 3, 4> : gens<1, 1, 2, 3, 4> : gens<0, 0, 1, 2, 3, 4>
. Ostatni typ specjalizuje się w tworzeniuseq<0, 1, 2, 3, 4>
. Całkiem sprytna sztuczka.gens
:template <int N, int... S> struct gens { typedef typename gens<N-1, N-1, S...>::type type; };
std::integer_sequence<T, N>
i dla ich specjalizacjistd::size_t
,std::index_sequence<N>
- plus związanych z nimi funkcji pomocniczychstd::make_in(teger|dex)_sequence<>()
istd::index_sequence_for<Ts...>()
. A w C ++ 17 jest wiele innych dobrych rzeczy zintegrowanych z biblioteką - w tym w szczególnościstd::apply
istd::make_from_tuple
, które obsługiwałyby rozpakowywanie i wywoływanie bitówRozwiązaniem C ++ 17 jest po prostu użycie
std::apply
:Po prostu czułem, że należy to powiedzieć raz w odpowiedzi w tym wątku (po tym, jak już pojawił się w jednym z komentarzy).
W tym wątku wciąż brakuje podstawowego rozwiązania C ++ 14. EDYCJA: Nie, tak naprawdę jest w odpowiedzi Waltera.
Ta funkcja jest podana:
Nazwij go następującym fragmentem:
Przykład:
PRÓBNY
źródło
http://coliru.stacked-crooked.com/a/8ea8bcc878efc3cb
std::make_unique
bezpośrednio? Czy potrzebuje konkretnej instancji funkcji? 2. Dlaczegostd::move(ts)...
, jeśli możemy zmienić[](auto... ts)
się[](auto&&... ts)
?std::make_unique
oczekujesz krotki, a krotkę można utworzyć z rozpakowanej krotki tylko za pomocą innego wywołania dostd::make_tuple
. To właśnie zrobiłem w lambda (chociaż jest bardzo redundantne, ponieważ można również po prostu skopiować krotkę do unikalnego wskaźnika bez użyciacall
).Jest to w pełni kompilowalna wersja rozwiązania Johannesa do pytania o awoodland, w nadziei, że może być dla kogoś przydatna. Zostało to przetestowane za pomocą migawki g ++ 4.7 na ściśnięciu Debiana.
Można użyć następującego pliku SConstruct
Na mojej maszynie to daje
źródło
Oto rozwiązanie C ++ 14.
To wciąż potrzebuje jednej funkcji pomocniczej (
call_func
). Ponieważ jest to powszechny idiom, być może standard powinien go wspierać bezpośrednio, jak wstd::call
przypadku możliwej implementacjiWtedy nasza opóźniona wysyłka staje się
źródło
std::call
. Chaotyczne zoointeger_sequence
iindex_sequence
typy pomocników C ++ 14 zostały wyjaśnione tutaj: en.cppreference.com/w/cpp/utility/integer_sequence Zwróć uwagę na wyraźną nieobecnośćstd::make_index_sequence(Args...)
, dlatego Walter został zmuszony do stosowania składni clunkierstd::index_sequence_for<Args...>{}
.Jest to nieco skomplikowane do osiągnięcia (nawet jeśli jest to możliwe). Radzę skorzystać z biblioteki, w której jest to już zaimplementowane, a mianowicie Boost.Fusion ( funkcja invoke ). Jako bonus, Boost Fusion współpracuje również z kompilatorami C ++ 03.
źródło
c ++ 14rozwiązanie. Po pierwsze, płyta użytkowa:
Umożliwiają one wywołanie lambda z szeregiem liczb całkowitych w czasie kompilacji.
i skończymy.
index_upto
iindex_over
pozwala pracować z pakietami parametrów bez konieczności generowania nowych zewnętrznych przeciążeń.Oczywiście w c ++ 17 ty tylko
Teraz, jeśli nam się to podoba, w c ++ 14 możemy pisać:
stosunkowo łatwo i zdobądź czystsze c ++ 17 Składnia gotowa do wysyłki.
wystarczy wymienić
notstd
zestd
kiedy twoi modernizacje kompilatora i Bob jest twoim wujem.źródło
std::apply
<- muzyka dla moich uszuindex_upto
i mniej elastyczny. ;) Spróbuj zadzwonićfunc
z argumentami do tyłu odpowiednio przy pomocyindex_upto
istd::apply
. Trzeba przyznać, kto, do licha, chce wywołać funkcję od krotki do tyłu.std::tuple_size_v
jest C ++ 17, więc dla rozwiązania C ++ 14, które musiałoby zostać zastąpionetypename std::tuple_size<foo>::value
value
nie jest typem. Ale i tak naprawione.sizeof...(Types)
. Podoba mi się twoje rozwiązanie beztypename
.Zastanawiając się nad tym problemem w oparciu o udzieloną odpowiedź, znalazłem inny sposób rozwiązania tego samego problemu:
Co wymaga zmiany implementacji
delayed_dispatch()
na:Działa to poprzez rekursywną konwersję
std::tuple
parametru na sam pakiet parametrów.call_or_recurse
jest potrzebna jako specjalizacja do zakończenia rekurencji z prawdziwym wywołaniem, które po prostu rozpakowuje skompletowany pakiet parametrów.Nie jestem pewien, czy to jest i tak „lepsze” rozwiązanie, ale to inny sposób myślenia i rozwiązywania go.
Jako inne alternatywne rozwiązanie, którego możesz użyć
enable_if
, aby stworzyć coś prawdopodobnie prostszego niż moje poprzednie rozwiązanie:Pierwsze przeciążenie pobiera jeszcze jeden argument z krotki i umieszcza go w pakiecie parametrów. Drugie przeciążenie pobiera pasujący zestaw parametrów, a następnie wykonuje prawdziwe wywołanie, przy czym pierwsze przeciążenie jest wyłączone w jedynym i jedynym przypadku, w którym drugi byłby wykonalny.
źródło
Moja odmiana rozwiązania od Johannesa przy użyciu C ++ 14 std :: index_sequence (i typ zwracanej funkcji jako parametr szablonu RetT):
źródło