Präprozessor oder Metaprogrammierungstrick gesucht:


  • Mod

    Hallo,

    angenommen ich habe irgendeine Art von Liste (Anzahl der Elemente ist zur Compilezeit bekannt), z.B.

    {element1, element2, element3}
    

    Daraus möchte ich gerne automatisiert eine Abbildung erzeugen, die jedem Element eine neue Liste zuweist, nach der Form:

    element1 -> {element1, element2, element3}
    element2 -> {element2, element3}
    element3 -> {element3}
    

    Fällt da jemandem irgendein Präprozessor oder Metaprogrammierungstrick ein, mit dem man sowas machen kann? Oder muss ich zu höheren Makrosprachen als Präprozessor greifen?

    Mein konkretes Problem sieht so aus, dass ich mit boost::fusion eine Typelist erstellt habe:

    typedef fusion::vector<type1,type2, type3> my_list;
    

    und nun für ein bestimmtes Problem so etwas brauche:

    typedef fusion::map<fusion::pair<type1, my_list>,
                        fusion::pair<type2, fusion::vector<type2, type3> >, 
                        fusion::pair<type3, fusion::vector<type3> > 
                       > my_list_of_lists;
    

    Leider habe ich noch ger keinen konkreten Ansatz zur Lösung, außer dass ich etwas mit pop_front rumgespielt habe und mir die konkreten Fälle bis zu einer gewissen Rekusrionstiefe von Hand hingeschrieben habe. Viel besser wäre natürlich eine allgemeine Lösung bis zu beliebigen Tiefen, aber da fällt mir leider nichts ein, wie man das umsetzen könnte.


  • Administrator

    Ich möchte zuerst noch ein paar Dinge anmerken:
    1. Ich habe noch nie mit Boost.Fusion programmiert 😉
    2. Ich hatte es zuerst mit boost::fusion::map probiert, aber irgendwie hatte ich Probleme beim abrufen über boost::fusion::result_of::value_at_key . Daher habe ich es jetzt so gemacht, wie es gerade ist. GetRightSequence holt wie in einer Map das Element raus, nur das der Key jeweils das erste Element in der Liste von Typlisten ist 😉
    3. Ich beweise nur, dass es möglich ist. Wahrscheinlich geht es noch einfacher, da ich mich, wie gesagt, mit Boost.Fusion nicht so auskenne.
    4. Es hat auch Code in der main und zum Beispiel OutputHelp , welcher dafür zuständig ist, dass man das Resultat auf der Konsole prüfen kann. Ich habe gerne etwas sichtbares und nicht nur ein BOOST_STATIC_ASSERT(boost::is_same<...>) . 😉
    5. Viel Spass beim verstehen! 😃

    #include <boost/fusion/container/vector.hpp>
    
    #include <boost/fusion/iterator/next.hpp>
    #include <boost/fusion/iterator/value_of.hpp>
    
    #include <boost/fusion/sequence/intrinsic/end.hpp>
    #include <boost/fusion/sequence/intrinsic/begin.hpp>
    #include <boost/fusion/sequence/intrinsic/empty.hpp>
    #include <boost/fusion/sequence/intrinsic/value_at.hpp>
    
    #include <boost/fusion/algorithm/transformation/pop_front.hpp>
    #include <boost/fusion/algorithm/transformation/push_back.hpp>
    
    #include <iostream>
    
    void pause()
    {
    	std::cout << "PAUSE" << std::endl;
    
    	std::cin.clear();
    	std::cin.ignore(std::cin.rdbuf()->in_avail());
    	std::cin.get();
    }
    
    //////////////////////////////////////////////////////////////////////////
    // Algorithmus
    
    template
    <
    	typename ResultSequenceT,
    	typename SequenceT,
    	typename IsEmptyT = typename boost::fusion::result_of::empty<SequenceT>::type
    >
    struct TransformAlgo_Helper
    {
    private:
    	typedef typename boost::fusion::result_of::push_back<
    		ResultSequenceT,
    		SequenceT
    	>::type NextResultSequence;
    
    	typedef typename boost::fusion::result_of::pop_front<
    		SequenceT
    	>::type ReducedSequence;
    
    public:
    	typedef typename TransformAlgo_Helper<
    		NextResultSequence,
    		ReducedSequence
    	>::Result Result;
    };
    
    template
    <
    	typename ResultSequenceT,
    	typename SequenceT
    >
    struct TransformAlgo_Helper<ResultSequenceT, SequenceT, boost::mpl::true_>
    {
    	typedef ResultSequenceT Result;
    };
    
    template<typename SequenceT>
    struct TransformAlgo
    {
    private:
    	typedef boost::fusion::vector0 ResultSequence;
    
    public:
    	typedef typename TransformAlgo_Helper<
    		ResultSequence,
    		SequenceT
    	>::Result Result;
    };
    
    //////////////////////////////////////////////////////////////////////////
    
    //////////////////////////////////////////////////////////////////////////
    // Hole richtige Sequenz
    
    template
    <
    	typename IterT,
    	typename EndT,
    	typename ValueT,
    	typename CurrentValueT
    		= typename boost::fusion::result_of::value_of<
    			typename boost::fusion::result_of::begin<
    				typename boost::fusion::result_of::value_of<
    					IterT
    				>::type
    			>::type
    		>::type
    >
    struct GetRightSequence_Helper
    {
    private:
    	typedef typename boost::fusion::result_of::next<IterT>::type NextT;
    
    public:
    	typedef typename GetRightSequence_Helper<NextT, EndT, ValueT>::Result Result;
    };
    
    template<typename IterT, typename ValueT, typename CurrentValueT>
    struct GetRightSequence_Helper
    <
    	IterT,
    	IterT,
    	ValueT,
    	CurrentValueT
    >;
    
    template<typename IterT, typename EndT, typename ValueT>
    struct GetRightSequence_Helper
    <
    	IterT,
    	EndT,
    	ValueT,
    	ValueT
    >
    {
    	typedef typename boost::fusion::result_of::value_of<IterT>::type Result;
    };
    
    template<typename SeqT, typename ValueT>
    struct GetRightSequence
    {
    private:
    	typedef typename boost::fusion::result_of::begin<SeqT>::type Iter;
    	typedef typename boost::fusion::result_of::end<SeqT>::type End;
    
    public:
    	typedef typename GetRightSequence_Helper<Iter, End, ValueT>::Result Result;
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Ausgabehilfe
    
    template<typename IterT, typename EndIterT>
    struct OutputHelp
    {
    	typedef typename boost::fusion::result_of::value_of<IterT>::type Value;
    	typedef typename boost::fusion::result_of::next<IterT>::type NextIter;
    
    	OutputHelp<NextIter, EndIterT> m;
    
    	OutputHelp()
    		: m()
    	{
    	}
    
    	void output()
    	{
    		std::cout << typeid(Value).name() << std::endl;
    		m.output();
    	}
    };
    
    template<typename IterT>
    struct OutputHelp<IterT, IterT>
    {
    	OutputHelp()
    	{
    	}
    
    	void output()
    	{
    	}
    };
    
    //////////////////////////////////////////////////////////////////////////
    
    int main()
    {
    	typedef boost::fusion::vector<char, double, int> Vec;
    	typedef TransformAlgo<Vec>::Result Result;
    
    	// char type
    	std::cout << "At char:" << std::endl;
    
    	typedef GetRightSequence<Result, char>::Result CharSeq;
    	typedef boost::fusion::result_of::begin<CharSeq>::type CharIter;
    	typedef boost::fusion::result_of::end<CharSeq>::type CharEnd;
    
    	OutputHelp<CharIter, CharEnd> charOutputHelp;
    	charOutputHelp.output();
    
    	std::cout << "\n\n";
    
    	// At 1
    	std::cout << "At double:" << std::endl;
    
    	typedef GetRightSequence<Result, double>::Result DoubleSeq;
    	typedef boost::fusion::result_of::begin<DoubleSeq>::type DoubleIter;
    	typedef boost::fusion::result_of::end<DoubleSeq>::type DoubleEnd;
    
    	OutputHelp<DoubleIter, DoubleEnd> doubleOutputHelp;
    	doubleOutputHelp.output();
    
    	std::cout << "\n\n";
    
    	// At 2
    	std::cout << "At int:" << std::endl;
    
    	typedef GetRightSequence<Result, int>::Result IntSeq;
    	typedef boost::fusion::result_of::begin<IntSeq>::type IntIter;
    	typedef boost::fusion::result_of::end<IntSeq>::type IntEnd;
    
    	OutputHelp<IntIter, IntEnd> intOutputHelp;
    	intOutputHelp.output();
    
    	std::cout << "\n\n";
    	pause();
    
    	return 0;
    }
    

    Grüssli


  • Mod

    Vielen Dank, wird aber wohl eine Weile dauern bis ich das durchschaut habe.

    edit Nachtrag: Am Wochenende hatte ich leider keine Zeit mir das anzugucken, aber jetzt habe ich alles durchschaut. Als kleinen Nachtrag für alle die es interessiert noch eine Demonstration, wie man Draveres Methode mit echten Runtime Objekten kombinieren kann.

    TODO: Ein Schönheitsfehler bleibt: Ich konnte fusion::pair einfach nicht dazu bekommen, Referenzen als Datentyp zu schlucken. Dadurch wird am Anfang einmal die komplette Liste unnötigerweise kopiert. Das ist zwar nicht so schlimm, da es ein einmaliger Vorgang ist, aber unschön ist es doch. Ich werde da mal noch ein wenig rumfrickeln bis es perfekt ist.

    Der Code:

    #include <iostream>
    #include <boost/fusion/include/container.hpp>
    #include <boost/fusion/include/algorithm.hpp>
    #include <boost/fusion/include/at_key.hpp>
    
    ///////////////////////////////////////////////////////////////////////
    // Ein paar Klassen um die Funktion zu prüfen
    //
    
    class classA{
    public:
      double x;
      classA(const double& xx): x(xx) {std::cout<<"Class A constructed "<<x<<std::endl;}
      classA(): x(1.){std::cout<<"Class A default constructed "<<x<<std::endl;}
      void member(){
        std::cout<<"Class A: "<<x<<std::endl;
      }
      classA(classA const& source): x(source.x){std::cout<<"Class A copy constructed "<<x<<std::endl;}
      ~classA(){std::cout<<"Class A deconstructed "<<x<<std::endl;}
    };
    
    class classB{
    public:
      int x;
      classB(const int& xx): x(xx){std::cout<<"Class B constructed "<<x<<std::endl;}
      classB(): x(2){std::cout<<"Class B default constructed "<<x<<std::endl;}
      void member(){
        std::cout<<"Class B: "<<x<<std::endl;
      }
      classB(classB const& source): x(source.x){std::cout<<"Class B copy constructed "<<x<<std::endl;}
      ~classB(){std::cout<<"Class B deconstructed "<<x<<std::endl;}
    };
    
    class classC{
    public:
      char x;
      classC(const int& xx): x(xx){std::cout<<"Class C constructed "<<x<<std::endl;}
      classC(): x('c'){std::cout<<"Class C default constructed "<<x<<std::endl;}
      void member(){
        std::cout<<"Class C: "<<x<<std::endl;
      }
      classC(classC const& source): x(source.x){std::cout<<"Class C copy constructed "<<x<<std::endl;}
      ~classC(){std::cout<<"Class C deconstructed "<<x<<std::endl;}
    };
    
    //////////////////////////////////////////////////////////////////////////////
    // Ausgabefunktionen zu den Klassen
    //
    
    template<typename T1, typename T2>  void foo(T1 & x1, T2 & x2);
    
    template<> void foo<classA,classA>(classA &x1, classA &x2){
      std::cout<<"A "<< x1.x<< " meets A "<< x2.x<< std::endl;
    }
    template<> void foo<classA,classB>(classA &x1, classB &x2){
      std::cout<<"A "<< x1.x<< " meets B "<< x2.x<< std::endl;
    }
    template<> void foo<classA,classC>(classA &x1, classC &x2){
      std::cout<<"A "<< x1.x<< " meets C "<< x2.x<< std::endl;
    }
    template<> void foo<classB,classA>(classB &x1, classA &x2){
      std::cout<<"B "<< x1.x<< " meets A "<< x2.x<< std::endl;
    }
    template<> void foo<classB,classB>(classB &x1, classB &x2){
      std::cout<<"B "<< x1.x<< " meets B "<< x2.x<< std::endl;
    }
    template<> void foo<classB,classC>(classB &x1, classC &x2){
      std::cout<<"B "<< x1.x<< " meets C "<< x2.x<< std::endl;
    }
    template<> void foo<classC,classA>(classC &x1, classA &x2){
      std::cout<<"C "<< x1.x<< " meets A "<< x2.x<< std::endl;
    }
    template<> void foo<classC,classB>(classC &x1, classB &x2){
      std::cout<<"C "<< x1.x<< " meets B "<< x2.x<< std::endl;
    }
    template<> void foo<classC,classC>(classC &x1, classC &x2){
      std::cout<<"C "<< x1.x<< " meets C "<< x2.x<< std::endl;
    }
    
    //////////////////////////////////////////////////////////////////////////
    // Algorithmus
    //
    
    template
    <
        typename ResultSequenceT,
        typename SequenceT,
        typename IsEmptyT = typename boost::fusion::result_of::empty<SequenceT>::type
    >
    struct TransformAlgo_Helper
    {
    private:
        typedef typename boost::fusion::result_of::push_back<
            ResultSequenceT,
            SequenceT
        >::type NextResultSequence;
    
        typedef typename boost::fusion::result_of::pop_front<
            SequenceT
        >::type ReducedSequence;
    
    public:
        typedef typename TransformAlgo_Helper<
            NextResultSequence,
            ReducedSequence
        >::Result Result;
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Hole richtige Sequenz
    //
    
    template
    <
        typename ResultSequenceT,
        typename SequenceT
    >
    struct TransformAlgo_Helper<ResultSequenceT, SequenceT, boost::mpl::true_>
    {
        typedef ResultSequenceT Result;
    };
    
    template<typename SequenceT>
    struct TransformAlgo
    {
    private:
        typedef boost::fusion::vector0 ResultSequence;
    
    public:
        typedef typename TransformAlgo_Helper<
            ResultSequence,
            SequenceT
        >::Result Result;
    };
    
    template
    <
        typename IterT,
        typename EndT,
        typename ValueT,
        typename CurrentValueT
            = typename boost::fusion::result_of::value_of<
                typename boost::fusion::result_of::begin<
                    typename boost::fusion::result_of::value_of<
                        IterT
                    >::type
                >::type
            >::type
    >
    struct GetRightSequence_Helper
    {
    private:
        typedef typename boost::fusion::result_of::next<IterT>::type NextT;
    
    public:
        typedef typename GetRightSequence_Helper<NextT, EndT, ValueT>::Result Result;
    };
    
    template<typename IterT, typename ValueT, typename CurrentValueT>
    struct GetRightSequence_Helper
    <
        IterT,
        IterT,
        ValueT,
        CurrentValueT
    >;
    
    template<typename IterT, typename EndT, typename ValueT>
    struct GetRightSequence_Helper
    <
        IterT,
        EndT,
        ValueT,
        ValueT
    >
    {
        typedef typename boost::fusion::result_of::value_of<IterT>::type Result;
    };
    
    template<typename SeqT, typename ValueT>
    struct GetRightSequence
    {
    private:
        typedef typename boost::fusion::result_of::begin<SeqT>::type Iter;
        typedef typename boost::fusion::result_of::end<SeqT>::type End;
    
    public:
        typedef typename GetRightSequence_Helper<Iter, End, ValueT>::Result Result;
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Ausgabehilfe
    //
    
    template<typename IterT, typename EndIterT, typename SecondValue, typename listentyp>
    struct OutputHelp
    {
        typedef typename boost::fusion::result_of::value_of<IterT>::type Value;
        typedef typename boost::fusion::result_of::next<IterT>::type NextIter;
    
      OutputHelp<NextIter, EndIterT, SecondValue, listentyp> m;
    
        OutputHelp(listentyp &list)
          : L(list), m(list)
        {
        }
    
        void output()
        {
          foo(boost::fusion::at_key<Value>(L),boost::fusion::at_key<SecondValue>(L));
          m.output();
        }
    private:
      listentyp& L;
    };
    
    template<typename IterT, typename SecondValue, typename listentyp>
    struct OutputHelp<IterT, IterT, SecondValue, listentyp>
    {
        OutputHelp(listentyp &list)
        {
        }
    
        void output()
        {
        }
    };
    
    /////////////////////////////////////////////////////////////////////////////
    // Schleife über alle Listenelemente
    //
    
    template<typename T2, typename listentyp> struct loop{
      loop(listentyp &list): L(list){};
      template<typename T1> void operator()(T1& T)const{
        typedef typename GetRightSequence<T2, typename T1::first_type>::Result Seq;
        typedef typename boost::fusion::result_of::begin<Seq>::type Iter;
        typedef typename boost::fusion::result_of::end<Seq>::type End;
        OutputHelp<Iter, End, typename T1::first_type, listentyp> OutputHelper(L);
        OutputHelper.output();
      }
    private:
      listentyp &L;
    };
    
    //////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////
    
    int main(){
    
      typedef boost::fusion::map<boost::fusion::pair<classA, classA>
                               , boost::fusion::pair<classB, classB> 
                               , boost::fusion::pair<classC, classC> 
                                > mylist;
    
      typedef boost::fusion::vector<classA, classB, classC> typelist;
      typedef TransformAlgo<typelist>::Result transformed_type;
    
      classA x(4);
      classB y(5);
      classC z('f');
      mylist liste(boost::fusion::make_pair<classA>(x)
    	      ,boost::fusion::make_pair<classB>(y)
    	      ,boost::fusion::make_pair<classC>(z)
    	       );
    
      for_each(liste, loop<transformed_type, mylist>(liste));
    }
    

    Die (kommentierte) Ausgabe:

    Class A constructed 4
    Class B constructed 5
    Class C constructed f
    Class C copy constructed f  // total unnötig
    Class B copy constructed 5  // total unnötig
    Class A copy constructed 4  // total unnötig
    Class A copy constructed 4  // total unnötig
    Class B copy constructed 5  // total unnötig
    Class C copy constructed f  // total unnötig
    Class A deconstructed 4     // total unnötig
    Class B deconstructed 5     // total unnötig
    Class C deconstructed f     // total unnötig
    A 4 meets A 4
    B 5 meets A 4
    C f meets A 4
    B 5 meets B 5
    C f meets B 5
    C f meets C f
    Class C deconstructed f     // total unnötig
    Class B deconstructed 5     // total unnötig
    Class A deconstructed 4     // total unnötig
    Class C deconstructed f
    Class B deconstructed 5
    Class A deconstructed 4
    

  • Mod

    Hmm, einfaches Editieren pusht den Thread anscheinend nicht.

    Da es sonst ja keiner sieht: push!



  • Wenn ich solchen Code sehe, wird mir einfach schwindelig. Die Loesung steht in keinem Verhaeltnis zum gestellten Problem. Schreib dir doch einfach selbst einen "Praeprozessor" in Perl, Python, Haskel oder Lisp, der das entsprechende Pattern matched und transformiert. Fuer solche Sachen ist C++ reichlich ungeeignet. Mir ist nur von Lisp & Co. bekannt, dass man Zugriff auf den Syntaxbaum innerhalb der Sprache ueber Macros hat.

    wenig rumfrickeln bis es perfekt ist

    Mir ist kein Beispiel bekannt, bei dem durch Frickeln Perfektion entstanden ist.



  • knivil schrieb:

    Wenn ich solchen Code sehe, wird mir einfach schwindelig. Die Loesung steht in keinem Verhaeltnis zum gestellten Problem. Schreib dir doch einfach selbst einen "Praeprozessor" in Perl, Python, Haskel oder Lisp, der das entsprechende Pattern matched und transformiert. Fuer solche Sachen ist C++ reichlich ungeeignet.

    Ich glaub hier gehts auch hauptsächlich um die Herausforderung, das Ganze mit C++ Sprachmitteln zu meistern. Einen Sourcecode-Verwurster in Perl kann ja jeder schreiben 🤡


  • Mod

    Die Lösung zu den Referenzenproblem ist übrigens ganz einfach: Nicht make_pair, sondern direkt den Konstruktor von pair verwenden. Der Rest sollte klar sein.

    Und ja: Es ging hauptsächlich um die Herausforderung (auch wenn am Ende Dravere den Großteil der Lösung entwickelt hat), weil ich wusste, dass es irgendwie gehen MUSS. Das es dann doch so umständlich wird, hätte ich jedoch auch nicht erwartet. Nun da die Lösung aber existiert, werde ich sie auch in Zukunft verwenden. Ist doch beeindruckend, wie weit man mit den Templates doch so kommt - dieser Code wird auf jedem System mit einem standardkonformen Compiler funktionieren.

    viele edits: Ganz schön viele Rechtschreibfehler. Ich weiß nicht, was bei diesem Beitrag mit mir los war.



  • knivil schrieb:

    Wenn ich solchen Code sehe, wird mir einfach schwindelig. Die Loesung steht in keinem Verhaeltnis zum gestellten Problem. Schreib dir doch einfach selbst einen "Praeprozessor" in Perl, Python, Haskel oder Lisp, der das entsprechende Pattern matched und transformiert. Fuer solche Sachen ist C++ reichlich ungeeignet. Mir ist nur von Lisp & Co. bekannt, dass man Zugriff auf den Syntaxbaum innerhalb der Sprache ueber Macros hat.

    wenig rumfrickeln bis es perfekt ist

    Mir ist kein Beispiel bekannt, bei dem durch Frickeln Perfektion entstanden ist.

    Ein selbstgeschriebener Präprozesser ist IMO noch viel mehr gefrickelt. Erstens schafft man damit eine unnötige und lästige Abhängigkeit, und zweitens kann man damit vieles nicht sauber lösen, wo mit Typen gearbeitet wird.
    Dazu wäre ein vollständiger C++ Parser nötig, und du wirst ja wohl kaum vorschlagen nen vollständigen C++ Parser in Perl zu schreiben, oder?

    Kurz: eine Template-Schlacht über ein paar hundert Zeilen ist IMO immernoch weniger Aufwand, und vor allem sauberer, als irgendein dummer Präprozessor.



  • @Dravere

    Ich würde dich wegen deinem Code-Style heiraten, interesse? bin jung, frisch und mag kein mfc/winapi.



  • heart schrieb:

    @Dravere

    Ich würde dich wegen deinem Code-Style heiraten, interesse? bin jung, frisch und mag kein mfc/winapi.

    Solche Anträge sind nur mit Bild des Posters/der Posterin erlaubt 😉



  • hustbaer schrieb:

    Kurz: eine Template-Schlacht über ein paar hundert Zeilen ist IMO immernoch weniger Aufwand, und vor allem sauberer, als irgendein dummer Präprozessor.

    Ich habe nur auf die Moeglichkeit eines eigenen Praeprozessors angesprochen. Ich bin eigentlich der Meinung, dass C++ fuer dieses Problem (welches uebergeordnete auch immer) die falsche Sprache ist oder aber eine andere Loesung (non template) besser geeignet waere. Fuer mich ist es einfach nur write-only Code und kaum von anderen wartbar. Wobei das sehr subjektiv ist. Trotzdem ist es schoen, sowas mal zu sehen, aber in Ruecksichtnahme auf andere Programmierer wuerde ich es wahrscheinlich nicht verwenden.


Anmelden zum Antworten