Wie erweitere ich ein Tupel in die Argumente der variablen Vorlagenfunktion?

135

Betrachten Sie den Fall einer Vorlagenfunktion mit variablen Vorlagenargumenten:

template<typename Tret, typename... T> Tret func(const T&... t);

Jetzt habe ich ein Tupel tvon Werten. Wie rufe ich func()mit den Tupelwerten als Argumente auf? Ich habe über das bind()Funktionsobjekt mit call()Funktion und auch über die apply()Funktion in verschiedenen inzwischen veralteten Dokumenten gelesen . Die Implementierung von GNU GCC 4.4 scheint eine call()Funktion in der bind()Klasse zu haben, aber es gibt nur sehr wenig Dokumentation zu diesem Thema.

Einige Leute schlagen handgeschriebene rekursive Hacks vor, aber der wahre Wert variadischer Vorlagenargumente besteht darin, sie in Fällen wie oben verwenden zu können.

Hat jemand eine Lösung dafür oder einen Hinweis darauf, wo man darüber lesen kann?

Xeo
quelle
5
Der C ++ 14 Standard hat eine Lösung siehe; open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
Skeen
1
Die Idee ist , das Tupel in einem einzigen variadische Explosion entpacken, verwenden integer_sequence, siehe en.cppreference.com/w/cpp/utility/integer_sequence
Skeen
6
Wenn Sie eine haben integer_sequence S, rufen Sie einfach Ihre Funktion als auf func(std::get<S>(tuple)...)und lassen den Compiler den Rest erledigen.
Skeen
1
Wenn Sie C ++ 17 oder höher verwenden, ignorieren Sie diese Antwort und sehen Sie die folgende mit std :: apply
lewis

Antworten:

46

Hier ist mein Code, wenn jemand interessiert ist

Grundsätzlich rollt der Compiler zur Kompilierungszeit rekursiv alle Argumente in verschiedenen Inklusivfunktionsaufrufen ab. <N> -> ruft <N-1> -> Aufrufe auf ... -> ruft <0> auf. Dies ist der letzte und der Compiler optimiert Die verschiedenen Zwischenfunktionsaufrufe behalten nur den letzten bei, der func entspricht (arg1, arg2, arg3, ...).

Es werden 2 Versionen bereitgestellt, eine für eine Funktion, die für ein Objekt aufgerufen wird, und die andere für eine statische Funktion.

#include <tr1/tuple>

/**
 * Object Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_obj_func
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_obj_func<N-1>::applyTuple( pObj, f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_obj_func<0>
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    (pObj->*f)( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename T, typename... ArgsF, typename... ArgsT >
void applyTuple( T* pObj,
                 void (T::*f)( ArgsF... ),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_obj_func<sizeof...(ArgsT)>::applyTuple( pObj, f, t );
}

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_func
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_func<N-1>::applyTuple( f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_func<0>
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    f( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename... ArgsF, typename... ArgsT >
void applyTuple( void (*f)(ArgsF...),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_func<sizeof...(ArgsT)>::applyTuple( f, t );
}

// ***************************************
// Usage
// ***************************************

template < typename T, typename... Args >
class Message : public IMessage
{

  typedef void (T::*F)( Args... args );

public:

  Message( const std::string& name,
           T& obj,
           F pFunc,
           Args... args );

private:

  virtual void doDispatch( );

  T*  pObj_;
  F   pFunc_;
  std::tr1::tuple<Args...> args_;
};

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
Message<T, Args...>::Message( const std::string& name,
                              T& obj,
                              F pFunc,
                              Args... args )
: IMessage( name ),
  pObj_( &obj ),
  pFunc_( pFunc ),
  args_( std::forward<Args>(args)... )
{

}

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
void Message<T, Args...>::doDispatch( )
{
  try
  {
    applyTuple( pObj_, pFunc_, args_ );
  }
  catch ( std::exception& e )
  {

  }
}
David
quelle
2
Ist es möglich, dies anzupassen, um in einem Fall zu funktionieren, in dem die fragliche "Funktion" tatsächlich ein Konstruktor ist?
HighCommander4
Könnten Sie ein Beispiel dafür geben, was Sie tun möchten, und wir können von dort aus fortfahren.
David
Diese Lösung bietet nur einen Overhead für die Kompilierungszeit und wird am Ende zu (pObj -> * f) (arg0, arg, 1, ... argN) vereinfacht. richtig?
Goofy
Ja, der Compiler komprimiert die mehreren Funktionsaufrufe in den letzten, als hätten Sie sie selbst geschrieben, was das Schöne an all diesen Meta-Programmier-Dingen ist.
David
Das ganze tr1Zeug kann jetzt mit c ++ 11 herausgenommen werden
Ryan Haining
37

In C ++ 17 können Sie dies tun:

std::apply(the_function, the_tuple);

Dies funktioniert bereits in Clang ++ 3.9 mit std :: experimentelle :: apply.

Als Antwort auf den Kommentar, dass dies nicht funktioniert, wenn the_functioneine Vorlage erstellt wird, wird Folgendes umgangen:

#include <tuple>

template <typename T, typename U> void my_func(T &&t, U &&u) {}

int main(int argc, char *argv[argc]) {

  std::tuple<int, float> my_tuple;

  std::apply([](auto &&... args) { my_func(args...); }, my_tuple);

  return 0;
}

Diese Problemumgehung ist eine vereinfachte Lösung für das allgemeine Problem, Überlastsätze und Funktionsvorlagen zu übergeben, bei denen eine Funktion erwartet wird. Die allgemeine Lösung (eine, die sich um perfekte Weiterleitung, Konsequenz und Nichtausnahme kümmert) wird hier vorgestellt: https://blog.tartanllama.xyz/passing-overload-sets/ .

Mohammad Alaggan
quelle
Gemäß dem Beispielcode bei std :: apply scheint es nicht zu funktionieren, wenn the_functiones als Vorlage verwendet wird.
Zitrax
1
@ Zitrax Sie können die Vorlagenargumente der Funktion angeben:std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
Erbureth sagt Reinstate Monica
Dies ist die einfachste und eleganteste Lösung. Und es wirkt Wunder. Vielen Dank, M. Alaggan !!!!!! +100 Stimmen
Elliott
36

In C ++ gibt es viele Möglichkeiten, Tupel zu erweitern / zu entpacken und diese Tupelelemente auf eine variable Vorlagenfunktion anzuwenden. Hier ist eine kleine Hilfsklasse, die ein Indexarray erstellt. Es wird häufig in der Metaprogrammierung von Vorlagen verwendet:

// ------------- UTILITY---------------
template<int...> struct index_tuple{}; 

template<int I, typename IndexTuple, typename... Types> 
struct make_indexes_impl; 

template<int I, int... Indexes, typename T, typename ... Types> 
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...> 
{ 
    typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type; 
}; 

template<int I, int... Indexes> 
struct make_indexes_impl<I, index_tuple<Indexes...> > 
{ 
    typedef index_tuple<Indexes...> type; 
}; 

template<typename ... Types> 
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...> 
{}; 

Jetzt ist der Code, der die Arbeit erledigt, nicht so groß:

 // ----------UNPACK TUPLE AND APPLY TO FUNCTION ---------
#include <tuple>
#include <iostream> 

using namespace std;

template<class Ret, class... Args, int... Indexes > 
Ret apply_helper( Ret (*pf)(Args...), index_tuple< Indexes... >, tuple<Args...>&& tup) 
{ 
    return pf( forward<Args>( get<Indexes>(tup))... ); 
} 

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), const tuple<Args...>&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), tuple<Args...>(tup));
}

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), tuple<Args...>&&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), forward<tuple<Args...>>(tup));
}

Der Test ist unten dargestellt:

// --------------------- TEST ------------------
void one(int i, double d)
{
    std::cout << "function one(" << i << ", " << d << ");\n";
}
int two(int i)
{
    std::cout << "function two(" << i << ");\n";
    return i;
}

int main()
{
    std::tuple<int, double> tup(23, 4.5);
    apply(one, tup);

    int d = apply(two, std::make_tuple(2));    

    return 0;
}

Ich bin kein großer Experte für andere Sprachen, aber ich denke, wenn diese Sprachen keine solche Funktionalität in ihrem Menü haben, gibt es keine Möglichkeit, dies zu tun. Zumindest mit C ++ können Sie, und ich denke, es ist nicht so kompliziert ...

Sigidagi
quelle
"... und diese Tupelelemente auf eine variable Vorlagenfunktion anwenden" . Der Testabschnitt enthält jedoch nur nicht vorlagenvariable Funktionen. Wenn ich ein Like hinzufüge template<class ... T> void three(T...) {}und versuche, es anzuwenden, wird es nicht kompiliert.
Zitrax
32

Ich finde, dass dies die eleganteste Lösung ist (und sie wird optimal weitergeleitet):

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a)
        -> decltype(Apply<N-1>::apply(
            ::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        ))
    {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a)
        -> decltype(::std::forward<F>(f)(::std::forward<A>(a)...))
    {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t)
    -> decltype(Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t)))
{
    return Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

Anwendungsbeispiel:

void foo(int i, bool b);

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&foo, t);
}

Leider kann GCC (mindestens 4.6) dies nicht mit "sorry, nicht implementiert: Mangling Overload" kompilieren (was einfach bedeutet, dass der Compiler die C ++ 11-Spezifikation noch nicht vollständig implementiert hat), und da er verschiedene Vorlagen verwendet, wird dies nicht der Fall sein Arbeit in MSVC, so ist es mehr oder weniger nutzlos. Sobald es jedoch einen Compiler gibt, der die Spezifikation unterstützt, ist dies meiner Meinung nach der beste Ansatz. (Hinweis: Es ist nicht so schwer, dies zu ändern, damit Sie die Mängel in GCC umgehen oder mit Boost Preprocessor implementieren können, aber es ruiniert die Eleganz, daher ist dies die Version, die ich veröffentliche.)

GCC 4.7 unterstützt diesen Code jetzt einwandfrei.

Bearbeiten: Vorwärts um den tatsächlichen Funktionsaufruf hinzugefügt, um das r-Wert-Referenzformular * zu unterstützen, falls Sie clang verwenden (oder wenn jemand anderes tatsächlich dazu kommt, es hinzuzufügen).

Bearbeiten: Es wurde ein fehlender Vorlauf um das Funktionsobjekt im Hauptteil der Nicht-Mitglied-Apply-Funktion hinzugefügt. Vielen Dank an pheedbaq für den Hinweis, dass es fehlte.

Edit: Und hier ist die C ++ 14-Version, nur weil sie so viel schöner ist (noch nicht kompiliert):

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a) {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a) {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t) {
    return Apply< ::std::tuple_size< ::std::decay_t<T>
      >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

Hier ist eine Version für Mitgliedsfunktionen (nicht sehr getestet!):

using std::forward; // You can change this if you like unreadable code or care hugely about namespace pollution.

template<size_t N>
struct ApplyMember
{
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply(C&& c, F&& f, T&& t, A&&... a) ->
        decltype(ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...))
    {
        return ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...);
    }
};

template<>
struct ApplyMember<0>
{
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply(C&& c, F&& f, T&&, A&&... a) ->
        decltype((forward<C>(c)->*forward<F>(f))(forward<A>(a)...))
    {
        return (forward<C>(c)->*forward<F>(f))(forward<A>(a)...);
    }
};

// C is the class, F is the member function, T is the tuple.
template<typename C, typename F, typename T>
inline auto apply(C&& c, F&& f, T&& t) ->
    decltype(ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t)))
{
    return ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t));
}
// Example:

class MyClass
{
public:
    void foo(int i, bool b);
};

MyClass mc;

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&mc, &MyClass::foo, t);
}
DRayX
quelle
1
+1 von den aufgelisteten Antworten war Ihre die beste, die ich mit Argumenten arbeiten konnte, deren Argumente Vektoren sind ... ... aber ich erhalte immer noch Kompilierungsfehler. ideone.com/xH5kBH Wenn Sie dies mit -DDIRECT_CALL kompilieren und ausführen, werden Sie sehen, wie die Ausgabe aussehen soll. Ansonsten erhalte ich einen Kompilierungsfehler (ich denke, decltype ist nicht klug genug, um meinen Sonderfall herauszufinden) mit gcc 4.7.2.
kfmfe04
3
Die Version von gcc auf ideaone ist zu alt, als dass dies passieren könnte. Sie unterstützt nicht die Überladung des verstümmelten Rückgabetyps decltype. Ich habe diesen Code in gcc 4.7.2 relativ gründlich getestet und bin auf keine Probleme gestoßen. Mit gcc 4.8 können Sie die neue Funktion für automatische Rückgabewerte in C ++ 17 verwenden, um alle unangenehmen nachfolgenden Rückgabetypen vom Dekltyp zu vermeiden.
DRayX
1
applyWarum wird in der Nichtmitgliedsfunktion aus Neugier fnicht mit einem std::forwardAufruf umgebrochen, wie dies beim Rückgabetyp der Fall ist? Wird es nicht benötigt?
Brett Rossier
3
Aus Neugier habe ich versucht, dies in GCC 4.8 zu foo('x', true)kompilieren , und genau den gleichen Assembler-Code kompiliert wie apply(foo, ::std::make_tuple('x', true))bei jeder anderen Optimierungsstufe als -O0.
DRayX
2
Mit C ++ 14 erhalten integer_sequenceSie sogar eine fast korrekte Implementierung apply()in seinem Beispiel. siehe meine Antwort unten.
PeterSom
28
template<typename F, typename Tuple, std::size_t ... I>
auto apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template<typename F, typename Tuple>
auto apply(F&& f, Tuple&& t) {
    using Indices = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
    return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
}

Dies wird aus dem C ++ 14-Entwurf mit index_sequence angepasst. Ich könnte vorschlagen, mich in einem zukünftigen Standard (TS) zu bewerben.

PeterSom
quelle
1

Die Nachrichten sehen nicht gut aus.

Nachdem ich den gerade veröffentlichten Standardentwurf gelesen habe , sehe ich keine eingebaute Lösung dafür, was seltsam erscheint.

Der beste Ort, um nach solchen Dingen zu fragen (falls Sie dies noch nicht getan haben), ist comp.lang.c ++. Moderiert, da einige Leute dort regelmäßig an der Erstellung des Standardposts beteiligt sind.

Wenn Sie sich diesen Thread ansehen, hat jemand die gleiche Frage (vielleicht sind Sie es, in diesem Fall finden Sie diese ganze Antwort ein wenig frustrierend!), Und es werden einige hässliche Implementierungen vorgeschlagen.

Ich habe mich nur gefragt, ob es einfacher wäre, die Funktion a akzeptieren zu lassen tuple, da die Konvertierung auf diese Weise einfacher ist. Dies impliziert jedoch, dass alle Funktionen Tupel als Argumente für maximale Flexibilität akzeptieren sollten, und dies zeigt nur die Seltsamkeit, keine integrierte Erweiterung von Tupel zu Funktionsargumentpaket bereitzustellen.

Update: Der obige Link funktioniert nicht - versuchen Sie Folgendes einzufügen:

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/750fa3815cdaac45/d8dc09e34bbb9661?lnk=gst&q=tuple+variadic#d8dc09e34bbb9661

Daniel Earwicker
quelle
Ich frage mich, warum sie sich überhaupt die Mühe machen, getrennte Vorstellungen von Tupel- und Funktionsargumenten zu haben. Vielleicht sind sie in einem konformen Compiler austauschbar, aber ich habe nirgendwo einen Hinweis darauf gefunden, den ich über sie gelesen habe.
Daniel Earwicker
2
Da Tupel <int, char, string> als separater Typ erforderlich ist; ebenso wie die Fähigkeit, eine Funktion zu erstellen, für die make_type nicht in der Mitte jedes Aufrufs erforderlich ist.
Coppro
1
Außerdem ist der beste Ort nicht comp.lang.c ++. Moderiert. Fragen zu C ++ 1x richten sich fast immer besser an comp.std.c ++.
Coppro
1

Alle diese Implementierungen sind gut. Aufgrund der Verwendung des Zeigers auf den Elementfunktions-Compiler kann der Zielfunktionsaufruf jedoch häufig nicht inline geschaltet werden (zumindest gcc 4.8 kann dies nicht, egal was passiert. Warum kann gcc keine bestimmbaren Funktionszeiger inline schalten? ).

Die Dinge ändern sich jedoch, wenn der Zeiger auf die Elementfunktion als Vorlagenargumente und nicht als Funktionsparameter gesendet wird:

/// from https://stackoverflow.com/a/9288547/1559666
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; };

template<typename TT>
using makeSeq = typename gens< std::tuple_size< typename std::decay<TT>::type >::value >::type;


// deduce function return type
template<class ...Args>
struct fn_type;

template<class ...Args>
struct fn_type< std::tuple<Args...> >{

    // will not be called
    template<class Self, class Fn>
    static auto type_helper(Self &self, Fn f) -> decltype((self.*f)(declval<Args>()...)){
        //return (self.*f)(Args()...);
        return NULL;
    }
};

template<class Self, class ...Args>
struct APPLY_TUPLE{};

template<class Self, class ...Args>
struct APPLY_TUPLE<Self, std::tuple<Args...>>{
    Self &self;
    APPLY_TUPLE(Self &self): self(self){}

    template<class T, T (Self::* f)(Args...),  class Tuple>
    void delayed_call(Tuple &&list){
        caller<T, f, Tuple >(forward<Tuple>(list), makeSeq<Tuple>() );
    }

    template<class T, T (Self::* f)(Args...), class Tuple, int ...S>
    void caller(Tuple &&list, const seq<S...>){
        (self.*f)( std::get<S>(forward<Tuple>(list))... );
    }
};

#define type_of(val) typename decay<decltype(val)>::type

#define apply_tuple(obj, fname, tuple) \
    APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
            decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
            &decay<decltype(obj)>::type::fname \
            > \
            (tuple);

Und ussage:

struct DelayedCall
{  
    void call_me(int a, int b, int c){
        std::cout << a+b+c;
    }

    void fire(){
        tuple<int,int,int> list = make_tuple(1,2,3);
        apply_tuple(*this, call_me, list); // even simpler than previous implementations
    }
};

Nachweis von inlinable http://goo.gl/5UqVnC


Mit kleinen Änderungen können wir "überladen" apply_tuple:

#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(X,##__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
#define VARARG_IMPL_(base, count, ...) base##count(__VA_ARGS__)
#define VARARG_IMPL(base, count, ...) VARARG_IMPL_(base, count, __VA_ARGS__)
#define VARARG(base, ...) VARARG_IMPL(base, VA_NARGS(__VA_ARGS__), __VA_ARGS__)

#define apply_tuple2(fname, tuple) apply_tuple3(*this, fname, tuple)
#define apply_tuple3(obj, fname, tuple) \
    APPLY_TUPLE<typename decay<decltype(obj)>::type, typename decay<decltype(tuple)>::type >(obj).delayed_call< \
            decltype( fn_type< type_of(tuple) >::type_helper(obj, &decay<decltype(obj)>::type::fname) ), \
            &decay<decltype(obj)>::type::fname \
            /* ,decltype(tuple) */> \
            (tuple);
#define apply_tuple(...) VARARG(apply_tuple, __VA_ARGS__)

...

apply_tuple(obj, call_me, list);
apply_tuple(call_me, list);       // call this->call_me(list....)

Außerdem ist dies die einzige Lösung, die mit Vorlagenfunktionen funktioniert.

Turm120
quelle
1

1) Wenn Sie eine vorgefertigte parameter_pack-Struktur als Funktionsargument haben, können Sie std :: tie einfach wie folgt verwenden:

template <class... Args>
void tie_func(std::tuple<Args...> t, Args&... args)
{
 std::tie<Args...>(args...) = t;
}

int main()
{
 std::tuple<int, double, std::string> t(2, 3.3, "abc");

 int i;
 double d;
 std::string s;

 tie_func(t, i, d, s);

 std::cout << i << " " << d << " " << s << std::endl;
}

2) Wenn Sie kein fertiges Parampack-Argument haben, müssen Sie das Tupel so abwickeln

#include <tuple>
#include <functional>
#include <iostream>



template<int N>
struct apply_wrap {
    template<typename R, typename... TupleArgs, typename... UnpackedArgs>
    static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>& t, UnpackedArgs... args )
    {
        return apply_wrap<N-1>::applyTuple( f, t, std::get<N-1>( t ), args... );
    }
};


template<>
struct apply_wrap<0>
{
    template<typename R, typename... TupleArgs, typename... UnpackedArgs>
    static R applyTuple( std::function<R(TupleArgs...)>& f, const std::tuple<TupleArgs...>&, UnpackedArgs... args )
    {
        return f( args... );
    }
};



template<typename R, typename... TupleArgs>
R applyTuple( std::function<R(TupleArgs...)>& f, std::tuple<TupleArgs...> const& t )
{
    return apply_wrap<sizeof...(TupleArgs)>::applyTuple( f, t );
}



int fac(int n)
{
    int r=1;
    for(int i=2; i<=n; ++i)
        r *= i;
    return r;
}



int main()
{
    auto t = std::make_tuple(5);
    auto f = std::function<decltype(fac)>(&fac);
    cout << applyTuple(f, t);
}
sdd
quelle
0

Wie wäre es damit:

// Warning: NOT tested!
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

using std::declval;
using std::forward;
using std::get;
using std::integral_constant;
using std::size_t;
using std::tuple;

namespace detail
{
    template < typename Func, typename ...T, typename ...Args >
    auto  explode_tuple( integral_constant<size_t, 0u>, tuple<T...> const &t,
     Func &&f, Args &&...a )
     -> decltype( forward<Func>(f)(declval<T const>()...) )
    { return forward<Func>( f )( forward<Args>(a)... ); }

    template < size_t Index, typename Func, typename ...T, typename ...Args >
    auto  explode_tuple( integral_constant<size_t, Index>, tuple<T...> const&t,
     Func &&f, Args &&...a )
     -> decltype( forward<Func>(f)(declval<T const>()...) )
    {
        return explode_tuple( integral_constant<size_t, Index - 1u>{}, t,
         forward<Func>(f), get<Index - 1u>(t), forward<Args>(a)... );
    }
}

template < typename Func, typename ...T >
auto  run_tuple( Func &&f, tuple<T...> const &t )
 -> decltype( forward<Func>(f)(declval<T const>()...) )
{
    return detail::explode_tuple( integral_constant<size_t, sizeof...(T)>{}, t,
     forward<Func>(f) );
}

template < typename Tret, typename ...T >
Tret  func_T( tuple<T...> const &t )
{ return run_tuple( &func<Tret, T...>, t ); }

Die run_tupleFunktionsvorlage nimmt das angegebene Tupel und übergibt seine Elemente einzeln an die angegebene Funktion. Es führt seine Arbeit aus, indem es seine Hilfsfunktionsvorlagen rekursiv aufruft explode_tuple. Es ist wichtig, dass run_tupledie Größe des Tupels an übergeben wird explode_tuple. Diese Zahl dient als Zähler für die Anzahl der zu extrahierenden Elemente.

Wenn das Tupel leer ist, wird run_tupledie erste Version von explode_tuplemit der Remote-Funktion als einzigem anderen Argument aufgerufen. Die Remote-Funktion wird ohne Argumente aufgerufen und wir sind fertig. Wenn das Tupel nicht leer ist, wird eine höhere Nummer explode_tuplezusammen mit der Remote-Funktion an die zweite Version von übergeben . Ein rekursiver Aufruf anexplode_tuplewird mit denselben Argumenten erstellt, außer dass die Zählernummer um eins verringert wird und (ein Verweis auf) das letzte Tupelelement als Argument nach der Remote-Funktion angeheftet wird. Bei einem rekursiven Aufruf ist entweder der Zähler nicht Null, und ein weiterer Aufruf wird ausgeführt, wobei der Zähler wieder verringert wird und das nächste nicht referenzierte Element nach der Remote-Funktion, jedoch vor den anderen eingefügten Argumenten, in die Argumentliste eingefügt wird, oder der Zähler erreicht Null und die Remote-Funktion wird mit allen danach akkumulierten Argumenten aufgerufen .

Ich bin nicht sicher, ob ich die Syntax habe, eine bestimmte Version einer Funktionsvorlage zu erzwingen. Ich denke, Sie können einen Zeiger auf die Funktion als Funktionsobjekt verwenden. Der Compiler wird das Problem automatisch beheben.

CTMacUser
quelle
0

Ich evaluiere MSVS 2013RC und es konnte in einigen Fällen einige der hier vorgeschlagenen vorherigen Lösungen nicht kompilieren. Zum Beispiel kann MSVS "auto" -Rückgaben nicht kompilieren, wenn zu viele Funktionsparameter vorhanden sind, da die Namespace-Imbrikation begrenzt ist (ich habe diese Informationen an Microsoft gesendet, um sie korrigieren zu lassen). In anderen Fällen benötigen wir Zugriff auf die Rückgabe der Funktion, obwohl dies auch mit einer Lamda möglich ist: Die folgenden beiden Beispiele liefern das gleiche Ergebnis.

apply_tuple([&ret1](double a){ret1 = cos(a); }, std::make_tuple<double>(.2));
ret2 = apply_tuple((double(*)(double))cos, std::make_tuple<double>(.2));

Und nochmals vielen Dank an diejenigen, die hier vor mir Antworten gepostet haben, ohne sie wäre ich nicht dazu gekommen ... also hier ist es:

template<size_t N>
struct apply_impl {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&& t, A&&... a)
    -> decltype(apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return apply_impl<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
    -> decltype(apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return apply_impl<N-1>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t),
                          std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)...);
    }
};

// This is a work-around for MSVS 2013RC that is required in some cases
#if _MSC_VER <= 1800 /* update this when bug is corrected */
template<>
struct apply_impl<6> {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&& t, A&&... a)
    -> decltype(std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return std::forward<F>(f)(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&& t, A&&... a)
    -> decltype((o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...)) {
         return (o->*std::forward<F>(f))(std::get<0>(std::forward<T>(t)), std::get<1>(std::forward<T>(t)), std::get<2>(std::forward<T>(t)),
           std::get<3>(std::forward<T>(t)), std::get<4>(std::forward<T>(t)), std::get<5>(std::forward<T>(t)), std::forward<A>(a)...);
    }
};
#endif

template<>
struct apply_impl<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply_tuple(F&& f, T&&, A&&... a)
    -> decltype(std::forward<F>(f)(std::forward<A>(a)...)) {
         return std::forward<F>(f)(std::forward<A>(a)...);
    }
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply_tuple(C*const o, F&& f, T&&, A&&... a)
    -> decltype((o->*std::forward<F>(f))(std::forward<A>(a)...)) {
         return (o->*std::forward<F>(f))(std::forward<A>(a)...);
    }
};

// Apply tuple parameters on a non-member or static-member function by perfect forwarding
template<typename F, typename T>
inline auto apply_tuple(F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t))) {
     return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t));
}

// Apply tuple parameters on a member function
template<typename C, typename F, typename T>
inline auto apply_tuple(C*const o, F&& f, T&& t)
-> decltype(apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t))) {
     return apply_impl<std::tuple_size<typename std::decay<T>::type>::value>::apply_tuple(o, std::forward<F>(f), std::forward<T>(t));
}
lap777
quelle
Warum machen Sie das Objektargument zu einem konstanten Zeiger? Keine Referenz, keine konstante Referenz, nicht nur ein Zeiger? Was ist, wenn die aufrufbare Funktion dies nicht tut const?
Turm 120
0

Wenn Sie die Lösung von @ David erweitern, können Sie eine rekursive Vorlage schreiben, die

  1. Verwendet nicht die (übermäßig ausführliche, imo) integer_sequenceSemantik
  2. Verwendet keinen zusätzlichen temporären Vorlagenparameter, int Num rekursive Iterationen zu zählen
  3. (Optional für statische / globale Funktoren) verwendet den Funktor als Vorlagenparameter für die Optimierung zur Kompilierungszeit

Z.B:

template <class F, F func>
struct static_functor {
    template <class... T, class... Args_tmp>
    static inline auto apply(const std::tuple<T...>& t, Args_tmp... args)
            -> decltype(func(std::declval<T>()...)) {
        return static_functor<F,func>::apply(t, args...,
                std::get<sizeof...(Args_tmp)>(t));
    }
    template <class... T>
    static inline auto apply(const std::tuple<T...>& t, T... args)
            -> decltype(func(args...)) {
        return func(args...);
    }
};

static_functor<decltype(&myFunc), &myFunc>::apply(my_tuple);

Wenn Ihr Funktor zur Kompilierungszeit nicht definiert ist (z. B. eine Nicht- constexprFunktor-Instanz oder ein Lambda-Ausdruck), können Sie ihn alternativ als Funktionsparameter anstelle eines Klassenvorlagenparameters verwenden und die enthaltende Klasse tatsächlich vollständig entfernen:

template <class F, class... T, class... Args_tmp>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        Args_tmp... args) -> decltype(func(std::declval<T>()...)) {
    return apply_functor(func, t, args..., std::get<sizeof...(Args_tmp)>(t));
}
template <class F, class... T>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        T... args) -> decltype(func(args...)) {
    return func(args...);
}

apply_functor(&myFunc, my_tuple);

Für Callables mit Zeiger-zu-Mitglied-Funktion können Sie eines der oben genannten Codeteile ähnlich wie in der Antwort von @ David anpassen.

Erläuterung

In Bezug auf den zweiten Code gibt es zwei Vorlagenfunktionen: Die erste enthält den Funktor func, das Tupel tmit Typen T...und ein Parameterpaket argsmit Typen Args_tmp.... Beim Aufruf werden die Objekte von tAnfang ( 0) bis Ende rekursiv nacheinander zum Parameterpaket hinzugefügt und die Funktion mit dem neuen inkrementierten Parameterpaket erneut aufgerufen.

Die Signatur der zweiten Funktion ist fast identisch mit der ersten, außer dass der Typ T...für das Parameterpaket verwendet wird args. Sobald argsdie erste Funktion vollständig mit den Werten von gefüllt ist, lautet tihr Typ T...(im Pseudocode typeid(T...) == typeid(Args_tmp...)), und der Compiler ruft stattdessen die zweite überladene Funktion auf, die wiederum aufruft func(args...).

Der Code im Beispiel für den statischen Funktor funktioniert identisch, wobei der Funktor stattdessen als Klassenvorlagenargument verwendet wird.

CrepeGoat
quelle
Alle Kommentare zur Optimierung der Kompilierungszeit der ersten Option sind willkommen, damit ich meine Antwort vollständiger gestalten (und vielleicht etwas Neues lernen kann).
CrepeGoat
-3

Warum packen Sie Ihre variadischen Argumente nicht einfach in eine Tupelklasse und verwenden dann die Rekursion zur Kompilierungszeit (siehe Link ), um den Index abzurufen, an dem Sie interessiert sind. Ich finde, dass das Entpacken variadischer Vorlagen in einen Container oder eine Sammlung möglicherweise nicht typsicher für heterogene Typen ist

template<typename... Args>
auto get_args_as_tuple(Args... args) -> std::tuple<Args...> 
{
    return std::make_tuple(args);
}
outro56
quelle
6
Die Frage war umgekehrt. Nicht Args...-> tuple, sondern tuple-> Args....
Xeo
-4

Diese einfache Lösung funktioniert für mich:

template<typename... T>
void unwrap_tuple(std::tuple<T...>* tp)
{
    std::cout << "And here I have the tuple types, all " << sizeof...(T) << " of them" << std::endl;
}

int main()
{
    using TupleType = std::tuple<int, float, std::string, void*>;

    unwrap_tuple((TupleType*)nullptr); // trick compiler into using template param deduction
}
Maciek Gajewski
quelle