webentwicklung-frage-antwort-db.com.de

Tupel "entpacken", um einen passenden Funktionszeiger aufzurufen

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?

246
Flexo

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) ...);
  }
// ...
268

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
45
Faheem Mitha

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);    
}

DEMO

43
davidhigh

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&params, 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&params)
{ 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); }
};
41
Walter

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.

18
Karel Petranek

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.

3
Flexo

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;
}
2
schwart