Variadic Template class + friend class



  • Einen wunderschönen guten Abend,

    ich arbeite momentan an einem relativ großen Projekt und bin auf ein Problem gestoßen und habe meine Möglichkeiten eine Lösung zu finden ausgeschöpft.

    Und zwar geht es um eine Template Klasse mit variabler Template Argumentanzahl die auf die Argumente rekursiv eine statische Funktion aufruft. Als Rekursionsabbruch ist auch eine Template Klasse mit leeren Template Argumenten in der ebenfalls eine statische Methode drin ist die am Schluss aufgerufen werden soll.

    Das ist wenn alles public ist kein Problem, aber ich möchte gerne diese Methoden private haben und gerne mit friend dafür sorgen, dass nur die Klasse mit den variablen Template Argumenten auf diese Klasse für den Rekursionsabbruch zugreifen kann. Soll halt dazu dienen dass niemand anderes als die obere Klasse darauf zugreifen kann weil der Code nur für diese und keine andere Klasse bestimmt ist.

    Vielleicht wird es mit dem Code anschaulicher:

    template<typename... Args> class input;
    
    template<typename Head, typename... Tail> class input<Head, Tail...>
    {
    private:
    	static std::string create_regex()
    	{
    		// return "bsp str" + input<Tail…>::create_regex(); 
    	}
    };
    
    template<> class input<>
    {
    private:
    	static std::string create_regex()
    	{
    		return std::string{};
    	}
    };
    

    Vielleicht konnte ich das Problem hinreichend genau beschreiben. Vielleicht weiß auch jemand Rat ich würde mich sehr freuen sitze da schon einige Stunden dran und alle Versuche enden mit der Fehlermeldung, dass ich auf private Member nicht zugreifen kann oder ähnliches.

    Viele Grüße,

    Jan



  • Hab bisschen rumgespielt, bin nur auf das hier gekommen (C++17) :

    template<class... Tail>
    std::string create_regex() {
    	return (... + Tail::create_regex());
    }
    
    
    class Bar {
    private:
    	static std::string create_regex() {
    		return " Bar ";
    	}
    
    	template<class... Args>
    	friend std::string create_regex();
    };
    
    class Foo {
    private:
    	static std::string create_regex() {
    		return " Foo ";
    	}
    
    	template<class... Args>
    	friend std::string create_regex();
    };
    
    int main() {
    	std::cout << create_regex<Foo, Bar>();
    }
    

    Du wirst auf jeden Fall nicht darum herumkommen, dass jede Klasse, welche eine create_regex() Funktion definieren möchte, auch in irgendeiner Art und Weise eine entsprechende friend Deklaration braucht.



  • Wie wärs mit traits und Spezialisierung?



  • Die erste Lösung schaut sehr interessant aus, allerdings ist dann wieder das Problem was ich mit public hatte. Auf die Funktion kann jeder Zugreifen obwohl es nur die andere Klasse dürfen soll.

    Ist es nicht so, dass wenn die große Klasse auf die kleine Klasse zugreifen will muss eine friend Deklaration in der kleinen Klasse stehen?

    Hier nochmal der Code mit einer kleinen Ergänzung in der Klasse unten. Ich habe die Stelle schon in allen möglichen Variationen da stehen gehabt aber der Compiler weigert sich sturr meinen Wunsch zu erfüllen

    template<typename... Args> class input;
    
    // Große Klasse
    template<typename Head, typename... Tail> class input<Head, Tail...>
    {
    private:
    	static std::string create_regex()
    	{
    		// return "bsp str" + input<Tail…>::create_regex(); 
    	}
    };
    
    // Kleine Klasse
    template<> class input<>
    {
    private:
            // friend irgendwas; // sowas in der Richtung würde ich gerne machen
    
    	static std::string create_regex()
    	{
    		return std::string{};
    	}
    };
    

    Der Ansatz mit traits und Spezialisierung ist auch sehr interessant. Ich beherrsche schon wie man Templateargumente nach Typen überprüft aber Zugriffssteuerung das ist wirklich etwas wo ich etwas ratlos bin^^



  • @CAres91 Ich verstehe Deinen Punkt nicht. Die statischen Memberfunktionen sind in meinem Beispiel private. Es kann nur die Funktion create_regex() darauf zugreifen. Und an irgendeinem Punkt wird das ja immer nach Außen verfügbar sein, egal ob deine Lösung über eine freistehende Funktion läuft, oder über eine Klasse (wobei ich noch nicht verstehe, warum Du das über diese komische input Klasse machen willst). Größtes Manko: Du zeigst garnicht, wie die input Klasse verwendet werden soll. Ich habe ja auch ein minimales Beispiel erstellt inkl. main() Funktion. Natürlich kannst Du das auch innerhalb einer privaten Memberfunktion machen anstatt in einer freistehenden Funktion.

    #include <iostream>
    
    template<class... Args> class input
    {
    public:
    	static void printRegex() {
    		std::cout << create_regex();
    	}
    
    private:
    	static std::string create_regex()
    	{
    		return (... + Args::create_regex());
    	}
    };
    
    class Bar {
    private:
    	static std::string create_regex() {
    		return " Bar ";
    	}
    
    	template<class... Args>
    	friend class input;
    };
    
    class Foo {
    private:
    	static std::string create_regex() {
    		return " Foo ";
    	}
    
    	template<class... Args>
    	friend class input;
    };
    
    int main() {
    	input<Foo, Bar>::printRegex();
    }
    


  • Ich muss gestehen, dass ich mich bei dem Konzept an das Buch C++ Templates gehalten habe. Wenn ich das richtig sehe umgehe ich bei deiner Argumentation die Klasse ohne Template Parameter und ist direkt noch C++17 Standard.

    Naja Absicht ist dass man zum Beispiel
    "input<int, int, int, std::string> in1{"filename.txt"};" schreibt sodass man dann direkt die entsprechenden Datenaus den Zeilen der Datei holen kann und in ein entsprechendes Tupel bzw. Container der entsprechende Tupel schreiben kann, sodass man anschließend mit den Daten arbeiten kann.

    PS: Ich habe mit deinen beiden Ansätzen etwas herumgespielt und werde sie nicht 1 zu 1 umsetzen, aber ich hatte eventuell eine Idee die deutlich weniger umständlich ist als das was ich da fabriziert habe 😃 Danke für deinen Denkanstoß. Habe bislang aber nur einmal mit der C++17 Variante für variadische Templates gearbeitet aber ich denke das verkürzt einiges bei mir wenn alles klappt.



  • @CAres91 Ja, mit den neueren Sprachfeatures umgeht man i.d.R. diesen Head+Tail Ansatz, wobei man auch immer noch die "terminierende" Variante schreiben musste, welche den leeren Tail behandelt. Ehrlich gesagt schreibe ich selbst praktisch nie variadic templates, und ich kannte früher auch nur diese "alte" Variante aus einem Stroustrup Buch.

    Dazu nebenbei auch ein Tipp von mir: Die hilfreichste Informationen bekommst Du meistens, wenn Du Dein eigentliches Ziel schilderst, zusätzlich zu deiner aktuellen Lösung, um das Ziel zu erreichen. Oft ist es nämlich der Fall, dass die Experten hier eine noch bessere Lösung kennen, auf die man vielleicht sonst nicht gekommen wäre.

    Wenn ich sowas sehe wie:

    input<int, int, int, std::string> in1{"filename.txt"};
    

    Dann sehe ich vor Augen

    while (in1 >> num1 >> num2 >> num3 >> str) {
        // process data
    }
    

    ... und keinen regex weit und breit.



  • @HarteWare sagte in Variadic Template class + friend class:

    ... und keinen regex weit und breit.

    Sicher?
    Keine Ahnung, wer hier was genau meint. Aber seinen Code interpretiere ich als "Parsen" -> er will ein tuple<int, int, int, string> aus der Datei haben (oder für jede Zeile).

    Und was du geschrieben hast würde ich als "Schreiben" interpretieren, also genau umgekehrt.
    Wobei ich mir auch vorstellen könnte, dass du mit dieser Syntax eine Eingabestruktur beschreiben willst, das würde ich aber eher seltsam finden.



  • @Mechanics korrekt ich will einen Parser schreiben und mit den Template Argumenten bekommt die Klasse mitgeteilt welchen Aufbau die Zeilen haben. Dem Konstruktor geb ich dann den Dateinamen mit und die fertig extrahierten Daten sind in einem vector<tuple<…>> welche ich über einen getter zurückgebe um damit wie bei uns Algorithmen aus der Uni VL anwende.

    @HarteWare Ja da muss ich noch etwas üben in der Formulierung 😃

    Ich bin doch wieder zurück zu meiner alten Lösung aber ich habe alles in eine äußere Klasse verpackt. Die innere ist in der äußeren private also kann von Außen niemand dran rumfuschen. Die Member der inneren wollte ich auch erst private machen aber dann wäre wieder das friend Problem, aber selbst wenn ich die public mache kann nur die Umgebende Klasse darauf zugreifen.

    Ich glaube das mit den Faltungsausdrücken in meinem Zusammenhang funktioniert nicht denn ich will nicht irgendwas für beliebige Funktionsargumente beliebig vieler Typen machen sondern ich brauche nur die Template Argumente. Hab den ganzen Abend gestern damit rumgespielt aber bin auf keinen Grünen zweig gekommen leider 😕



  • @Mechanics Naja... es hat einfach keinen Informationsgehalt, wenn da steht input<int, int, int, std::string> und da bringt mir regex nichts. Bestenfalls habe ich jetzt die Typen für meine Tuple. Woher soll die input Klasse wissen, in welcher Form da nun drei Integer und ein String vorkommen? Es gibt praktisch unendlich viele Möglichkeiten, und die einfache und logische habe ich schon dargestellt. Vielleicht ist hier mein Vorstellungsvermögen oder Wissen zu Parsern einfach zu gering... Ich bin nie über den handgeschriebenen recursive descent parser hinausgegangen.

    @CAres91

    mit den Template Argumenten bekommt die Klasse mitgeteilt welchen Aufbau die Zeilen haben

    Das ist genau mein Punkt: <int, int, int, std::string> sagt über den Aufbau der Daten nichts aus!

    P.S.: Mit Python wirst Du hier auch fast sicher schneller (Entwicklungszeit) und einfacher diese magische Datei Parsen 😉



  • Ich hab da jetzt eigentlich kein Problem mit dem Ansatz.
    Ob das "gut" ist, mag ich jetzt nicht beurteilen. Aber wenn die Datei diesen Aufbau hat, und man genau das haben will, sollte das in der Form durchaus gehen.

    Ich würde hier vermutlich auch tatsächlich Richtung traits schauen... Du brauchst ja vermutlich nicht viel Logik dahinter.
    Wenn du hier vor allem an eigene Klassen wie point oder was auch immer denkst, sollten sie eher gar keine create_regex Funktion haben, die macht an der Klasse keinen Sinn. Man könnte man einen RegexBuilder<point> spezialisieren.


Anmelden zum Antworten