Ich versuche, eine variierende Anzahl von Werten in einem std::Tuple
Zu speichern, die später als Argumente für einen Aufruf eines Funktionszeigers verwendet werden, der den gespeicherten Typen entspricht.
Ich habe ein vereinfachtes Beispiel erstellt, das das Problem zeigt, um das ich mich bemühe, es zu lösen:
#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();
}
Normalerweise würde ich bei Problemen mit std::Tuple
Oder unterschiedlichen Vorlagen eine andere Vorlage wie template <typename Head, typename ...Tail>
Schreiben, um alle Typen nacheinander rekursiv auszuwerten, aber ich sehe keine Möglichkeit, dies für den Versand zu tun ein Funktionsaufruf.
Die eigentliche Motivation dafür ist etwas komplexer und es ist meist sowieso nur eine Lernübung. Sie können davon ausgehen, dass ich das Tupel vertraglich von einer anderen Schnittstelle ausgehändigt habe. Es kann also nicht geändert werden, aber der Wunsch, es in einen Funktionsaufruf zu entpacken, liegt bei mir. Dies schließt die Verwendung von std::bind
Als billige Methode aus, um das zugrunde liegende Problem zu umgehen.
Was ist eine saubere Methode, um den Anruf mit std::Tuple
Zu verteilen, oder eine alternative bessere Methode, um dasselbe Nettoergebnis beim Speichern/Weiterleiten einiger Werte und eines Funktionszeigers bis zu einem beliebigen zukünftigen Zeitpunkt zu erzielen?
Sie müssen ein Parameterpaket mit Zahlen erstellen und diese entpacken
template<int ...>
struct seq { };
template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };
template<int ...S>
struct gens<0, S...> {
typedef seq<S...> type;
};
// ...
void delayed_dispatch() {
callFunc(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
void callFunc(seq<S...>) {
func(std::get<S>(params) ...);
}
// ...
Dies ist eine vollständig kompilierbare Version von Johannes Lösung zu awoodlands Frage, in der Hoffnung, dass sie jemandem nützlich sein könnte. Dies wurde mit einem Schnappschuss von g ++ 4.7 unter Debian Squeeze getestet.
###################
johannes.cc
###################
#include <Tuple>
#include <iostream>
using std::cout;
using std::endl;
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
double foo(int x, float y, double z)
{
return x + y + z;
}
template <typename ...Args>
struct save_it_for_later
{
std::Tuple<Args...> params;
double (*func)(Args...);
double delayed_dispatch()
{
return callFunc(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
double callFunc(seq<S...>)
{
return func(std::get<S>(params) ...);
}
};
#pragma GCC diagnostic Push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
int main(void)
{
gens<10> g;
gens<10>::type s;
std::Tuple<int, float, double> t = std::make_Tuple(1, 1.2, 5);
save_it_for_later<int,float, double> saved = {t, foo};
cout << saved.delayed_dispatch() << endl;
}
#pragma GCC diagnostic pop
Man kann die folgende SConstruct-Datei verwenden
#####################
SConstruct
#####################
#!/usr/bin/python
env = Environment(CXX="g++-4.7", CXXFLAGS="-Wall -Werror -g -O3 -std=c++11")
env.Program(target="johannes", source=["johannes.cc"])
Auf meinem Rechner gibt dies
g++-4.7 -o johannes.o -c -Wall -Werror -g -O3 -std=c++11 johannes.cc
g++-4.7 -o johannes johannes.o
Die C++ 17-Lösung besteht einfach darin, std::apply
:
auto f = [](int a, double b, std::string c) { std::cout<<a<<" "<<b<<" "<<c<< std::endl; };
auto params = std::make_Tuple(1,2.0,"Hello");
std::apply(f, params);
Habe gerade das Gefühl, dass dies einmal in einer Antwort in diesem Thread angegeben werden sollte (nachdem es bereits in einem der Kommentare aufgetaucht ist).
Die grundlegende C++ 14-Lösung fehlt in diesem Thread noch. EDIT: Nein, es ist tatsächlich in der Antwort von Walter.
Diese Funktion ist gegeben:
void f(int a, double b, void* c)
{
std::cout << a << ":" << b << ":" << c << std::endl;
}
Nennen Sie es mit dem folgenden Snippet:
template<typename Function, typename Tuple, size_t ... I>
auto call(Function f, Tuple t, std::index_sequence<I ...>)
{
return f(std::get<I>(t) ...);
}
template<typename Function, typename Tuple>
auto call(Function f, Tuple t)
{
static constexpr auto size = std::Tuple_size<Tuple>::value;
return call(f, t, std::make_index_sequence<size>{});
}
Beispiel:
int main()
{
std::Tuple<int, double, int*> t;
//or std::array<int, 3> t;
//or std::pair<int, double> t;
call(f, t);
}
Hier ist eine C++ 14-Lösung.
template <typename ...Args>
struct save_it_for_later
{
std::Tuple<Args...> params;
void (*func)(Args...);
template<std::size_t ...I>
void call_func(std::index_sequence<I...>)
{ func(std::get<I>(params)...); }
void delayed_dispatch()
{ call_func(std::index_sequence_for<Args...>{}); }
};
Dies benötigt noch eine Hilfsfunktion (call_func
). Da dies eine gebräuchliche Redewendung ist, sollte der Standard sie möglicherweise direkt als std::call
Mit möglicher Implementierung unterstützen
// helper class
template<typename R, template<typename...> class Params, typename... Args, std::size_t... I>
R call_helper(std::function<R(Args...)> const&func, Params<Args...> const¶ms, std::index_sequence<I...>)
{ return func(std::get<I>(params)...); }
// "return func(params...)"
template<typename R, template<typename...> class Params, typename... Args>
R call(std::function<R(Args...)> const&func, Params<Args...> const¶ms)
{ return call_helper(func,params,std::index_sequence_for<Args...>{}); }
Dann wird unser verspäteter Versand
template <typename ...Args>
struct save_it_for_later
{
std::Tuple<Args...> params;
std::function<void(Args...)> func;
void delayed_dispatch()
{ std::call(func,params); }
};
Dies ist etwas kompliziert zu erreichen (obwohl es möglich ist). Ich rate Ihnen, eine Bibliothek zu verwenden, in der dies bereits implementiert ist, nämlich Boost.Fusion (die Funktion aufrufen ). Als Bonus arbeitet Boost Fusion auch mit C++ 03-Compilern zusammen.
c ++ 14 Lösung. Zunächst einige Utility Boilerplate:
template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
return [](auto&&f)->decltype(auto){
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t, N> ={}){
return index_over( std::make_index_sequence<N>{} );
}
Mit diesen können Sie ein Lambda mit einer Reihe von Ganzzahlen zur Kompilierungszeit aufrufen.
void delayed_dispatch() {
auto indexer = index_upto<sizeof...(Args)>();
indexer([&](auto...Is){
func(std::get<Is>(params)...);
});
}
und wir sind fertig.
index_upto
und index_over
Sie können mit Parameterpaketen arbeiten, ohne neue externe Überladungen generieren zu müssen.
Natürlich in c ++ 17 Sie gerade
void delayed_dispatch() {
std::apply( func, params );
}
Wenn uns das gefällt, können wir in c ++ 14 schreiben:
namespace notstd {
template<class T>
constexpr auto Tuple_size_v = std::Tuple_size<T>::value;
template<class F, class Tuple>
decltype(auto) apply( F&& f, Tuple&& tup ) {
auto indexer = index_upto<
Tuple_size_v<std::remove_reference_t<Tuple>>
>();
return indexer(
[&](auto...Is)->decltype(auto) {
return std::forward<F>(f)(
std::get<Is>(std::forward<Tuple>(tup))...
);
}
);
}
}
relativ einfach und machen Sie die sauberere c ++ 17 Syntax versandbereit.
void delayed_dispatch() {
notstd::apply( func, params );
}
ersetzen Sie einfach notstd
durch std
, wenn Ihr Compiler aktualisiert und bob Ihr Onkel ist.
Nachdenken über das Problem, basierend auf der gegebenen Antwort, ich habe einen anderen Weg gefunden, um das gleiche Problem zu lösen:
template <int N, int M, typename D>
struct call_or_recurse;
template <typename ...Types>
struct dispatcher {
template <typename F, typename ...Args>
static void impl(F f, const std::Tuple<Types...>& params, Args... args) {
call_or_recurse<sizeof...(Args), sizeof...(Types), dispatcher<Types...> >::call(f, params, args...);
}
};
template <int N, int M, typename D>
struct call_or_recurse {
// recurse again
template <typename F, typename T, typename ...Args>
static void call(F f, const T& t, Args... args) {
D::template impl(f, t, std::get<M-(N+1)>(t), args...);
}
};
template <int N, typename D>
struct call_or_recurse<N,N,D> {
// do the call
template <typename F, typename T, typename ...Args>
static void call(F f, const T&, Args... args) {
f(args...);
}
};
Wofür muss die Implementierung von delayed_dispatch()
folgendermaßen geändert werden:
void delayed_dispatch() {
dispatcher<Args...>::impl(func, params);
}
Dies funktioniert durch rekursives Konvertieren von std::Tuple
In ein eigenständiges Parameterpaket. call_or_recurse
Wird als Spezialisierung benötigt, um die Rekursion mit dem realen Aufruf zu beenden, der nur das fertige Parameterpaket entpackt.
Ich bin mir nicht sicher, ob dies eine "bessere" Lösung ist, aber es ist eine andere Art, darüber nachzudenken und sie zu lösen.
Als weitere alternative Lösung können Sie enable_if
Verwenden, um etwas zu bilden, das vermutlich einfacher ist als meine vorherige Lösung:
#include <iostream>
#include <functional>
#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...);
template <typename ...Actual>
typename std::enable_if<sizeof...(Actual) != sizeof...(Args)>::type
delayed_dispatch(Actual&& ...a) {
delayed_dispatch(std::forward<Actual>(a)..., std::get<sizeof...(Actual)>(params));
}
void delayed_dispatch(Args ...args) {
func(args...);
}
};
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();
}
Die erste Überladung übernimmt nur ein weiteres Argument aus dem Tupel und fügt es in ein Parameterpaket ein. Die zweite Überladung nimmt ein übereinstimmendes Parameterpaket entgegen und führt dann den realen Aufruf aus, wobei die erste Überladung in dem einzigen Fall deaktiviert ist, in dem die zweite überlebensfähig wäre.
Meine Variante der Lösung von Johannes mit der C++ 14 std :: index_sequence (und Funktionsrückgabetyp als Template-Parameter RetT):
template <typename RetT, typename ...Args>
struct save_it_for_later
{
RetT (*func)(Args...);
std::Tuple<Args...> params;
save_it_for_later(RetT (*f)(Args...), std::Tuple<Args...> par) : func { f }, params { par } {}
RetT delayed_dispatch()
{
return callFunc(std::index_sequence_for<Args...>{});
}
template<std::size_t... Is>
RetT callFunc(std::index_sequence<Is...>)
{
return func(std::get<Is>(params) ...);
}
};
double foo(int x, float y, double z)
{
return x + y + z;
}
int testTuple(void)
{
std::Tuple<int, float, double> t = std::make_Tuple(1, 1.2, 5);
save_it_for_later<double, int, float, double> saved (&foo, t);
cout << saved.delayed_dispatch() << endl;
return 0;
}