Variadic templates in Klassen und Funktionen



  • Hi,

    ich möchte gerne die variable Anzahl von Argumenten, die mir in einem Funktionsaufruf zugespielt wurde, speichern und sie zu einem späteren Zeitpunkt wieder an eine andere Funktion weiter geben.

    Ich habe mal versucht das Problem mit Quelltext zu visualisieren 😉

    #include <tuple>
    
    template< typename T, typename... P >
    class Foo
    {
    public :
      void
      setP( P... p );
    
      void
      init();
    
    private :
      T*                  t_; // Soll zu einem beliebigen Zeitpunkt initialisiert werden
      std::tuple< P... >  p_; // Haelt die Argumente fuer den Konstruktor von T
    };
    
    template< typename T, typename... P >
    void
    Foo< T, P... >::setP( P... p )
    {
      // Oder kann ich die argumente auch in anderer Form in Foo speichern?
      p_ = std::tuple< P... >( p... );
    }
    
    template< typename T, typename ...P >
    void
    Foo< T, P... >::init()
    {
      t_ = new T(); // Will ich nicht :(
      // Hier soll aber nicht der Standardkonstruktor und nicht ein Konstruktor,
      // der ein Tuple als Argument nimmt sonder einer der die einzelnen Elemente
      // des Tuple nimmt stehen.
      // Etwa sowas:
      // t_ = new T( std::get< 0 >( p_ ), ...... , std::get< N >( p_ ) );
      // Denn in meinem Quelltext waere das sehr viel Aufwand alle Konstruktoren
      // auf Tuple nachzuruesten, die ich an dieser Stelle gerne verwenden wuerde :(
    }
    

    Was ich suche ist also die Möglichkeit ein Tuple wieder in eine variable Argumentenliste zu überführen.



  • Ich denke uns beiden ist klar, dass die Sprache keine Unterstützung dafür hat. std::tuple ist einfach nur ein Template und die variadic templates sind ein Sprachkonstrukt, d.h. das funktioniert nur in eine Richtung.

    Dann schreib die anderen Klassen halt um, sieh es als Lehrgeld 😕


  • Mod

    OMFG schrieb:

    Ich denke uns beiden ist klar, dass die Sprache keine Unterstützung dafür hat. std::tuple ist einfach nur ein Template und die variadic templates sind ein Sprachkonstrukt, d.h. das funktioniert nur in eine Richtung.

    Was zu beweisen wäre. Ich bezweifle jedenfalls, das wir ohne einige Erfahrung jetzt schon mit Sicherheit sagen können, was mit variadic templates möglich ist und was nicht.
    Für das Problem habe ich auch noch keine Lösung.
    Nehmen wir der Einfachheit halber zunächst an, dass jeder Typ im Tupel genau einmal vorkommt, dann müsste

    template< typename T, typename ...P >
    void
    Foo< T, P... >::init()
    {
      t_ = new T( std::get< pos_< P, decltype( p_ ) >::value >( p_ )... );
    }
    

    möglich sein. pos_ sei hier eine Metafunktion, die die Position eines Elements im Tupel bestimmt. So etwas ist definitiv möglich, bereits enthält boost::mpl alles dafür Nötige. Allgemeiner gestellt geht es darum, ob es möglich ist, aus einem Parameter-Pack
    typename ... P
    ein zweites der Form
    int ... i
    zu generieren, mit der Folge 0, 1, 2, 3, ... , sizeof...( P ) - 1.
    Mein Gefühl sagt mir, dass das möglich sein sollte, aber dafür sind ein paar Probleme aus dem Weg zu räumen.


  • Mod

    Ungefähr so könnte es evtl. gehen (ich habe gerade keine Möglichkeit, das zu testen) oder es gibt vielleicht einen Anstoß zur Lösung.

    #include <tuple>
    
    template< int..., typename...>
    struct pack_indices;
    
    template< int ... i, typename T, typename ... P >
    struct pack_indices< i..., T, P... >
    {
        typedef typename pack_indices< i..., sizeof...( i ), P... >::type type;
    };
    template< int ... i >
    struct pack_indices< i... >
    {
        typedef pack_indices type;
    };
    
    template< typename T, typename... P >
    class Foo
    {
    public :
      void
      setP( P... p );
    
      template< int ...i >
      void
      init(pack_indices< i... > = typename pack_indices< P... >::type);
    
    private :
      T*                  t_; // Soll zu einem beliebigen Zeitpunkt initialisiert werden
      std::tuple< P... >  p_; // Haelt die Argumente fuer den Konstruktor von T
    };
    
    template< typename T, typename ...P >
    typename< int ...i>
    void
    Foo< T, P... >::init(pack_indices< i... >)
    {
      t_ = new T( std::get< i >( p_ )... );
    ...
    }
    


  • Also wenn das mal kein Paradebeispiel dafür ist, dass die Syntax dieser ganzen Neuerungen einfach nur furchtbar ist.



  • Ich hab mal eingehender nach deinem Vorschlag gesucht und diese zwei Dinge ausgegraben:

    1. Beschreibung (im Abschnitt Template Metaprogramming)
    http://www.jot.fm/issues/issue_2008_02/article2/index.html

    2. Implementierung
    gcc-4.3.1/gcc/textsuite/g++.dg/cpp0x/variadic-bind.C

    ...
    // A tuple of integer values
    template<int...> struct int_tuple {};
    
    // make_indexes_impl is a helper for make_indexes
    template<int I, typename IntTuple, typename... Types>
    struct make_indexes_impl;
    
    template<int I, int... Indexes, typename T, typename... Types>
    struct make_indexes_impl<I, int_tuple<Indexes...>, T, Types...>
    {
      typedef typename make_indexes_impl<I+1,
                                         int_tuple<Indexes..., I>,
                                         Types...>::type type;
    };
    
    template<int I, int... Indexes>
    struct make_indexes_impl<I, int_tuple<Indexes...> > {
      typedef int_tuple<Indexes...> type;
    };
    
    // make_indexes takes a variable-length number of N types and
    // generates an int_tuple that contains <0, 1, 2, ..., N-1>. These can
    // be used as indexes for tuple's get or tuple_element operation.
    template<typename... Types> 
    struct make_indexes : make_indexes_impl<0, int_tuple<>, Types...> { };
    
    // Get the Ith tuple element, but only if I is in bounds.
    template<int I, typename Tuple, typename = void> 
    struct safe_tuple_element{ };
    
    template<int I, typename... Values>
    struct safe_tuple_element<I, tuple<Values...>, 
             typename enable_if<(I >= 0 && 
                                 I < tuple_size<tuple<Values...> >::value)
                                >::type>
    {
      typedef typename tuple_element<I, tuple<Values...> >::type type;
    };
    
    // mu maps a bound argument to an actual argument, given a tuple of
    // the arguments passed to the function object returned by bind().
    
    // Return the stored reference from reference_wrapper
    template<typename T, typename... Args>
    inline T& mu(reference_wrapper<T>& bound_arg, const tuple<Args&...>&)
    {
      return bound_arg.get();
    }
    
    // Unwrap a tuple into separate arguments and forward to the function
    // object f.
    template<typename F, int... Indexes, typename... Args>
    inline typename F::result_type
    unwrap_and_forward(F& f, int_tuple<Indexes...>, const tuple<Args&...>& args)
    {
      return f(get<Indexes>(args)...);
    }
    ...
    

    EDIT:
    Und benutzen tut man das ganze wie in diesem Artikel beschrieben:
    http://www.archivum.info/comp.std.c++/2006-08/msg00246.html



  • Kudos, camper!



  • Okay, wenn ich das mal mit der Syntax vergleiche, die meine momentane Lösung für genau dieses Problem hat...

    Das sind sicher 100 Zeilen Code und 15 Makros unterschied. Hammer.

    Nichtsdestotrotz bin ich mit der Allgemeinen Syntax noch nicht zufrieden. Verdammte rückwärtskompatibilität :(. Man könnte soviele Tolle Sachen so einfach machen, wenn man mal eine Generalisierung wie arrays=tuple=argumentliste machen würde. Aber nein...


Anmelden zum Antworten