[gelöst] Variablen durch Makro oder Preprozessor erzeugen



  • RockNix schrieb:

    Aber wie würde man das mit der Länge N und den Werten 0..N initialisieren ? Ich muss noch anmerken, das die Länge in einer 3rd Party Header Datei enthalten ist.

    🤡 🤡 🤡
    Na gut, schnell mal ein Makro gebastelt:

    #define merge2(a, b) a##b
    #define merge3(a, b, c) a##b##c
    
    #define D(p, a) int merge2(p, a) = index_counter_++;
    #define DECLARE10(p) D(p,0) D(p,1) D(p,2) D(p,3) D(p,4) D(p,5) D(p,6) D(p,7) D(p,8) D(p,9)
    
    #define D1(p, n) DECLARE10(merge2(p,n))
    #define DECLARE100(p) D1(p,0) D1(p,1) D1(p,2) D1(p,3) D1(p,4) D1(p,5) D1(p,6) D1(p,7) D1(p,8) D1(p,9)
    
    #define DECLARE256(p) DECLARE100(merge(p, 0)) DECLARE100(merge(p, 1)) D1(merge2(p, 2), 0)\
                                                                          D1(merge2(p, 2), 1)\
                                                                          D1(merge2(p, 2), 2)\
                                                                          D1(merge2(p, 2), 3)\
                                                                          D1(merge2(p, 2), 4)\
                                                                          D(merge3(p, 2, 5), 0)\
                                                                          D(merge3(p, 2, 5), 1)\
                                                                          D(merge3(p, 2, 5), 2)\
                                                                          D(merge3(p, 2, 5), 3)\
                                                                          D(merge3(p, 2, 5), 4)\
                                                                          D(merge3(p, 2, 5), 5)
    

    Benutzen mit

    typedef int const delc_type; /// Welchen Typ sollen die Variablen haben?
    unsigned index_counter_ = 0; /// Wo soll die Indizierung anfangen? Bei Null?
    DECLARE256(a);
    

    Deklariert 256 Variablen des Typs decl_type mit Identifier a000, a001, ..., a255.
    Es kann auch DECLARE100 usw. verwendet werden. Jedoch muss vor jedem Benutzen index_counter_ auf 0 gesetzt werden, sonst wird der Index der Variablen weitergezählt.



  • RockNix schrieb:

    Ja ein const Array käme auch in Betracht. Aber wie würde man das mit der Länge N und den Werten 0..N initialisieren ? Ich muss noch anmerken, das die Länge in einer 3rd Party Header Datei enthalten ist.

    Spaß beiseite, dann deklarierst du eben das Array im Header und initialisierst es in einem Source, und im Source hast du dann folgendes stehen:

    struct Unused__
    {
        Unused__()
        {
            int a = 0;
            while(a < sizeof ARRAY)
                ARRAY[a] = a++;
        }
    } Unused__Object;
    

    Solange du auf ARRAY (das ist jetzt mal bspw. der Name deines Arrays) nicht in Initialisierungen von globalen Variablen zugreifst, kommt es auch nicht zum static initialization order fiasco.


  • Mod

    RockNix schrieb:

    Ist es möglich folgende Variablen durch Makros oder Preprozessor Anweisungen in einer schleife zu erzeugen ?

    Ja, das ist möglich aber sinnlos.



  • Bis zu N = 256 ist Boost.Preprocessor dein Freund:

    #include <boost/preprocessor.hpp>
    
    #define LENGTH 200
    
    #define MAKE_VALUE(unused1, n, unused2) n
    
    int arr[LENGTH] = {
      BOOST_PP_ENUM(LENGTH, MAKE_VALUE, unused)
    };
    

    @Sone: Bezeichner, die __ enthalten, sind für die Implementation reserviert (also den Compiler respektive die Standardbibliothek). Du darfst sie in deinem Code streng genommen nicht benutzen.



  • seldon schrieb:

    @Sone: Bezeichner, die __ enthalten, sind für die Implementation reserviert (also den Compiler respektive die Standardbibliothek). Du darfst sie in deinem Code streng genommen nicht benutzen.

    Viel schlimmer sind allerdings Bezeichner wie merge , die sogar in der Standardbibliothek vorkommen.

    RockNix, wozu brauchst du das? Nehmen wir an, du hättest ein const int -Array mit Grösse N und array[i] == i . Warum musst du auf ein Element mit array[i] zugreifen, wieso kannst du nicht direkt i in den Code schreiben?



  • Nexus schrieb:

    seldon schrieb:

    @Sone: Bezeichner, die __ enthalten, sind für die Implementation reserviert (also den Compiler respektive die Standardbibliothek). Du darfst sie in deinem Code streng genommen nicht benutzen.

    Viel schlimmer sind allerdings Bezeichner wie merge , die sogar in der Standardbibliothek vorkommen.

    So, jetzt aber mal ehrlich. Das war schnell hingefrickelt, für die Lolz. Nicht als auch nur annähernd ernst gemeinte, realistische Alternative. :xmas1:

    Nexus schrieb:

    RockNix, wozu brauchst du das? [...] Warum musst du auf ein Element mit array zugreifen, wieso kannst du nicht direkt i in den Code schreiben?

    Das habe ich mich auch gerade gefragt. Was soll es bringen wenn ein 'a' vor dem Literal steht? :xmas2:

    Wie in allen Fällen kann ich auch nur ein 👍 geben für wozu brauchst du das?

    Edit: Nexus, Mann! Du hast die ganze Zeit offene BB-Code-Tags benutzt! 😃

    Edit²: Hab' jetzt, nur aus Prinzip, die Identifier im Code verändert, damit sich keiner mehr beschwert.



  • Morgen zusammen,

    erst einmal vielen Dank für das rege Interesse an meiner Frage.

    Ich habe mal ein kleines Beispiel zusammen geschrieben, welches die Konstansten "hard coded" enthält - hier eben a0-a3. Diese brauche ich wie zu sehen ist als Template Parameter.

    Und genau diesen würde ich gern als "N" variabel halten. N ist hierbei die möglich Anzahl von Geräten die mir von einem Treiberhersteller vorgegeben wird.

    Die statische callback ist sozusagen eine zu implementierende Callback Routine des Treibers. Leider liefert diese keinen Kontext zurück. Ich bin somit darauf angewiesen, mir selbst ein solche Umgebung zu schaffen, was aus meinem Beispiel ersichtlich auch ganz gut funktioniert, aber eben nicht mit dynamischer Länge.

    #include <iostream>
    
    const int a0 = 0;
    const int a1 = 1;
    const int a2 = 2;
    const int a3 = 3;
    
    class IA  // generisches treiber interface, exemplarisch
    {
    public:
    	virtual void exec() = 0;
    };
    
    template <typename int i> class B : public IA // treiber des herstellers A im speziellen
    {
    
    private:
    
    	int m_i;
    	static B* m_This;
    
    public:
    
    	B():m_i(i)
    	{
    		m_This = this;
    	}
    
    	virtual void exec()
    	{
    		std::cout << &callback << " m_i=" << m_This->m_i << std::endl;
    	}
    
    	static void callback()
    	{
    		return;
    	}
    };
    
    B<a0>* B<a0>::m_This;
    B<a1>* B<a1>::m_This;
    B<a2>* B<a2>::m_This;
    B<a3>* B<a3>::m_This;
    
    int main()
    {
    	IA* k[4];
    
    	k[0] = new B<a0>();
    	k[1] = new B<a1>();
    	k[2] = new B<a2>();
    	k[3] = new B<a3>();
    
    	for( int x=0; x<4; x++)
    		k[x]->exec();
    
    	return 0;
    }
    

    Ich habe kurzfristig überlegt, als eine Art "pre-build" Command ein Script zu starten, welches mir ein File mit der Anzahl N und der entsprechende Aufrufe generiert 😕



  • Und wieso genau brauchst du hier ai und kannst nicht direkt i übergeben?

    k[0] = new B<0>();
    

    Sowieso sehe ich den Vorteil eines Templateparameters nicht. Warum übergibst du i nicht im Konstruktor? Brauchst du verschiedene Typen für verschiedene Callbacks?



  • template <typename int i>
    

    Wait what?
    Ist das eine VC++-Exotik oder was?



  • Nexus schrieb:

    Und wieso genau brauchst du hier ai und kannst nicht direkt i übergeben?

    k[0] = new B<0>();
    

    Sowieso sehe ich den Vorteil eines Templateparameters nicht. Warum übergibst du i nicht im Konstruktor? Brauchst du verschiedene Typen für verschiedene Callbacks?

    Weil solche Konstrukte nur mit "const int" und nicht mit "int" funktionieren.

    Ohne Kontext in den Callbacks kann ich die Treiber nicht auseinanderhalten. Parameter im Konstruktor würden das Problem nicht lösen. Erst die Parametrierung dupliziert die statischen Methoden - man beachte die ausgegebenen Adressen in der exec() Methode - somit hätte jeder Treiber genau einen Satz callbacks anhand derer sich der Kontext ergibt !



  • sorry doppel post



  • Sone schrieb:

    template <typename int i>
    

    Wait what?
    Ist das eine VC++-Exotik oder was?

    Nö, eigentlich nicht. Ich glaube das läuft unter dem Topic "Template Specialization" ... o.G.



  • RockNix schrieb:

    Weil solche Konstrukte nur mit "const int" und nicht mit "int" funktionieren.

    Natürlich kannst du int -Literale wie 0 als Templateargumente übergeben. Aber ich nehme an, du willst direkt 0 bis N erstellen.

    Musst du nachträglich noch Random Access auf die einzelnen B haben? Sonst könntest du rekursiv alle Instanzen abspeichern:

    template <int I>
    class B
    {
        public:
            void exec()
            {
                ...
                next.exec(); 
            }
    
        private:
            B<I-1> next;
    };
    
    template <>
    class B<0>
    {
        public:
            void exec() {}
    };
    

    Anwendung:

    int main()
    {
        B<12> b;
        b.exec();
    }
    

    Bei zu vielen Elementen kann der Compiler wegen zu tiefer Rekursion abbrechen.

    RockNix schrieb:

    Nö, eigentlich nicht. Ich glaube das läuft unter dem Topic "Template Specialization" ... o.G.

    Er meint, dass die Syntax falsch ist. Das typename steht nur bei Typ-Parametern.



  • Ja, Random Access ist zwingend. Die exec() ist nur exemplarisch und vielleicht etwas unglücklich gewählt vom Namen her.

    Das generische Interface hat die üblichen Treiberfunktionen wie create, open, start etc. gekapselt. Und ich habe eben Treiber von verschiedenen Herstellern und auch von Verschiedenen Typen.

    Und es gibt eben einen Speziellen, der in seinem Callback eben keinen Context zurückliefert. Somit weiss ich nicht welcher Treiber zurückruft, wenn es meherer Instanzen vom selben Typ gibt.

    Im grunde das alte Problem mit den statischen Callbacks in Klassen, nur das man meist von der API irgendwelche Context Pointer oder User Parameter hat, die in diesem speziellen Fall eben fehlen.

    Ich bin mit dem Beispiel der Lösung eigentlich schon sehr nah, nur die Sache mit "N" ist noch offen.



  • Ich habe das Beispiel noch mal etwas geändert, um den Sinn besser heraus zu stellen:

    #include <iostream>
    
    const int a0 = 0;
    const int a1 = 1;
    const int a2 = 2;
    const int a3 = 3;
    
    class IA
    {
    public:
    	virtual void start() = 0;
    };
    
    template <int i> class B : public IA
    {
    
    private:
    
    	static B* m_This;
    
    public:
    
    	B()
    	{
    		m_This = this;
    	}
    
    	virtual void start()
    	{
    		std::cout << "start at instance: " << m_This << std::endl;
    	}
    
    	static void callback()
    	{
    		std::cout << "callback " << &callback << " at instance: " << m_This << std::endl; 
    		return;
    	}
    };
    
    B<a0>* B<a0>::m_This;
    B<a1>* B<a1>::m_This;
    B<a2>* B<a2>::m_This;
    B<a3>* B<a3>::m_This;
    
    int main()
    {
    	IA* k[4];
    
    	k[0] = new B<a0>();
    	k[1] = new B<a1>();
    	k[2] = new B<a2>();
    	k[3] = new B<a3>();
    
    	for( int x=0; x<4; x++)
    		k[x]->start();
    
    	// diese Aufrufe würden vom Treiber des Herstellers kommen
    	B<a0>::callback();
    	B<a1>::callback();
    	B<a2>::callback();
    	B<a3>::callback();
    
    	return 0;
    }
    

    Die Ausgabe ist wie folgt - man erkennt sehr schön, wie die statischen Callback quasi dupliziert wurden.

    start at instance: 00337960
    start at instance: 00337990
    start at instance: 003379C0
    start at instance: 003379F0
    callback 00821087 at instance: 00337960
    callback 00821069 at instance: 00337990
    callback 008211BD at instance: 003379C0
    callback 008211FE at instance: 003379F0
    
    Drücken Sie eine beliebige Taste . . .
    


  • Und was hält dich nun davon ab, es so zu machen?

    #include <iostream>
    
    class IA
    {
    public:
        virtual void start() = 0;
    };
    
    template <int i> class B : public IA
    {
    
    private:
    
        static B* m_This;
    
    public:
    
        B()
        {
            m_This = this;
        }
    
        virtual void start()
        {
            std::cout << "start at instance: " << m_This << std::endl;
        }
    
        static void callback()
        {
            std::cout << "callback " << &callback << " at instance: " << m_This << std::endl;
            return;
        }
    };
    
    B<0>* B<0>::m_This;
    B<1>* B<1>::m_This;
    B<2>* B<2>::m_This;
    B<3>* B<3>::m_This;
    
    int main()
    {
        IA* k[4];
    
        k[0] = new B<0>();
        k[1] = new B<1>();
        k[2] = new B<2>();
        k[3] = new B<3>();
    
        for( int x=0; x<4; x++)
            k[x]->start();
    
        // diese Aufrufe würden vom Treiber des Herstellers kommen
        B<0>::callback();
        B<1>::callback();
        B<2>::callback();
        B<3>::callback();
    
        return 0;
    }
    

    Dein Code compiliert übrigens nur mit dem Microsoft-Compiler, mit G++ beispielswise nicht.



  • Was mich davon abhält ? Gar nichts, ich suche nur noch die Lösung über N 😉

    Rein aus Interesse, was meckert denn der G++ an ?



  • Was g++ sagt, kannst du dir zb auf ideone.com angucken: http://ideone.com/dr9Wg5
    codepad.org mag deinen code auch nicht: http://codepad.org/elrfRQ4O
    Welcher Compiler dahinter steckt, weiß ich aber nicht. Es ist aber generell gut, sowas immer mal zu testen, damit der code auch standardkonform und portabel bleibt/wird.

    Ich meinte auch eher, warum du unbedingt diese Dinger mit const int a0, a1 usw. haben willst statt einfach direkt Literale zu verwenden.

    Dabei, das automatisch bis n zu haben, kann ich dir leider nicht helfen. Nexus' Lösung reicht ja auch nicht. Mal schaun, was noch so kommt hier. 🙂
    Komisch, dass das nötig ist, finde ich es trotzdem. 😉



  • Guter Tip, vielen Dank. Auch wenn g++ für mich kein Thema ist hat es mich schon interessiert und hier ist nun eine funktionierende Version. Schon erstaunlich was VC++ so alles durchgehen lässt. Macht man sich wenig Gedanken, wenn man mit anderen Compilern so gut wie nie in Berührung kommt.

    #include <iostream>
    
    const int a0 = 0;
    const int a1 = 1;
    const int a2 = 2;
    const int a3 = 3;
    
    class IA
    {
    public:
    	virtual void start() = 0;
    };
    
    template <int i> class B : public IA
    {
    private:
    	static B* m_This;
    public:
    	B();
    	virtual void start();
    	static void callback();
    };
    
    template<int i> B<i>::B()
    {
    	m_This = this;
    }
    
    template <int i> void B<i>::start()
    {
    	std::cout << "start at instance: " << m_This << std::endl;
    }
    
    template <int i> void B<i>::callback()
    {
    	std::cout << "callback " << &callback << " at instance: " << m_This << std::endl; 
    	return;
    }
    
    template<int i> B<i>* B<i>::m_This;
    
    int main()
    {
    	IA* k[4];
    
    	k[0] = new B<a0>();
    	k[1] = new B<a1>();
    	k[2] = new B<a2>();
    	k[3] = new B<a3>();
    
    	for( int x=0; x<4; x++)
    		k[x]->start();
    
    	// diese Aufrufe würden vom Treiber des Herstellers kommen
    	B<a0>::callback();
    	B<a1>::callback();
    	B<a2>::callback();
    	B<a3>::callback();
    
    	return 0;
    }
    

    Danke an alle erstmal an dieser Stelle für Eure Mühe !!!



  • Du machst so einiges komisch. Die Definition der statischen Variablen solltest du ein Mal schreiben, und nicht für jede Instanziierung. Die a -Variablen brauchst du nach wie vor nicht. Und natürlich musst du Destruktoren virtuell machen und Speicher wieder freigeben, aber ich hoffe, das tust du in deinem richtigen Projekt schon.

    Du kannst meinen Ansatz mit Rekursion auch auf ein Funktionstemplate anwenden. Unter Verwendung von C++11 ( std::array und std::unique_ptr ) kann ein sauberer Ansatz so aussehen:

    #include <array>
    #include <memory>
    #include <iostream>
    
    // int-to-type Idiom für Überladung
    template <unsigned int I>
    struct Int {};
    
    struct Base
    {
    	typedef std::unique_ptr<Base> Ptr;
    
    	virtual void exec() = 0;
    	virtual ~Base() {}
    };
    
    template <unsigned int I>
    struct Derived : Base
    {
    	virtual void exec() { std::cout << "Derived<" << I << ">\n"; }
    };
    
    // Template-rekursive Funktion
    template <unsigned int N, unsigned int I>
    void fillElements(std::array<Base::Ptr, N>& array, Int<I>)
    {
    	array[I].reset(new Derived<I>);
    	fillElements(array, Int<I+1>());
    }
    
    // Abbruchbedingung für Rekursion
    template <unsigned int N>
    void fillElements(std::array<Base::Ptr, N>&, Int<N>)
    {
    }
    

    Anwendung:

    int main()
    {
    	std::array<Base::Ptr, 10> elements; 
    	fillElements(elements, Int<0>());
    	// fertig!
    
    	for (auto itr = elements.begin(); itr != elements.end(); ++itr)
    		(*itr)->exec();
    }
    

    Wie du siehst, muss die Grösse nur ein einziges Mal angegeben werden. Du hast kein Copy&Paste, keine Schleife, kein Boilerplate-Code.


Anmelden zum Antworten