Absolute Typgrößen mit templates



  • hi,
    würdet ihr euch mal meine Lösung für die Erzeugung von Typen mit absoluter Größe anschauen?
    Habe das ganze in erster Linie zum Spaß geschrieben, für professionelle Zwecke ist boost definitiv vorzuziehen. Aber mir ging es in erster Linie darum es mit Standardmitteln zu lösen und nicht auf einen Compiler festzulegen.

    Ebenso kann man damit keinen 8Bit Typ auf einer 10Bit Maschiene erstellen, oder sonst eine beliebige Größe, aber dafür ist das ganze auch nicht gedacht.

    So genug der Worte hier ist einmal der Code und dazu noch nen paar Beispiele.

    Achja noch was, mir ist bewusst, dass long long nicht offiziell zum Standard gehört, aber es kann ja so gut wie jeder Compiler und außerdem wird der Code nur bei entsprechender Verwendung instanziert.

    #include <limits.h>
    
    template< bool condition, class TypeIfTrue, class TypeIfFalse >
    struct IfThenElse
    {
    };
    
    template< class TypeIfTrue, class TypeIfFalse >
    struct IfThenElse< true, TypeIfTrue, TypeIfFalse >
    {
    	typedef TypeIfTrue Type;
    };
    
    template< class TypeIfTrue, class TypeIfFalse >
    struct IfThenElse< false, TypeIfTrue, TypeIfFalse >
    {
    	typedef TypeIfFalse Type;
    };
    
    struct TypeSize
    {
    	enum
    	{
    		uchar	= sizeof( unsigned char ) * CHAR_BIT,
    		ushort	= sizeof( unsigned short ) * CHAR_BIT,
    		uint	= sizeof( unsigned int ) * CHAR_BIT,
    		ull		= sizeof( unsigned long long ) * CHAR_BIT
    	};
    };
    
    struct TypeTraits
    {
    	enum
    	{
    		NoSign,
    		WithSign
    	};
    };
    
    template< int size, int isSigned >
    struct BitsToType
    {
    };
    
    template< int isSigned >
    struct BitsToType< TypeSize::uchar, isSigned  >
    {
    	typedef typename IfThenElse< isSigned, signed char, unsigned char >::Type Type;
    };
    
    template< int isSigned >
    struct BitsToType< TypeSize::ushort, isSigned >
    {
    	typedef typename IfThenElse< isSigned, signed short, unsigned short >::Type Type;
    };
    
    template< int isSigned >
    struct BitsToType< TypeSize::uint, isSigned >
    {
    	typedef typename IfThenElse< isSigned, signed int, unsigned int >::Type Type;
    };
    
    template< int isSigned >
    struct BitsToType< TypeSize::ull, isSigned >
    {
    	typedef typename IfThenElse< isSigned, signed long long, unsigned long long >::Type Type;
    };
    
    //Verwendungsbeispiele:
    typedef typename BitsToType< 8, TypeTraits::WithSign >::Type int8;
    typedef typename BitsToType< 32, TypeTraits::NoSign >::Type uint32;
    
    //int8 var1 = -123;
    //uint32 var2 = 123456;
    


  • Ich muss zugeben, so richtig anfreunden kann ich mich damit noch nicht. Zumindest ist es keine allgemeine Lösung. Wer garantiert dir denn, dass deine TypeSize Enums alle einen unterschiedlichen Wert haben? Wenn nur zwei gleich sind, dann hast du auch zwei gleichwertige Spezialisierungen und dein Compiler streikt. Aber sicherlich kann man dafür eine Lösung finden. Ich könnte mir vorstellen, dass man zB alle Typen (und damit deren Grösse) durchgeht, bis ein passender gefunden wird. Wird keiner gefunden, gibts halt einen Compilefehler.
    Von der Idee her ist es zumindest ganz nett. Das kann einem einige Präprozessor Anweisungen ersparen.



  • Welche, #include <boost/cstdint.hpp> ? 😉



  • Sehe gerade habe int verwendet, dort sollte eigentlich long stehen und soweit ich weiß darf short int und long int nicht die gleiche Größe haben, daher sollte dieser Fall eigentlich nicht eintreten.

    Aber deine Idee hat auch etwas, da könnte ich mich mal an ner Typliste versuchen 🙂



  • du kannst eine Sepzialisierung in IfThenElse Weglassen und das mit dem unspezialisierten Template erschalgen.

    da du ja schon weisst das es Type X, Y Bits hat, sind kannst du noch ein zusätliches Dummy Templateparameter reinbaun, was auch bei gleicher Bitzahl unterschiedliche Spezialisierungen erzeugt.

    template< int size,typename DUMMYTYPE, int isSigned >
    struct BitsToType{};
    template< int isSigned >
    struct BitsToType< TypeSize::uint,int, isSigned  >{};
    template< int isSigned >
    struct BitsToType< TypeSize::ull,long, isSigned  >{};
    


  • b7f7 schrieb:

    du kannst eine Sepzialisierung in IfThenElse Weglassen und das mit dem unspezialisierten Template erschalgen.

    gute Idee, hab ich gleich mal eingebaut

    da du ja schon weisst das es Type X, Y Bits hat, sind kannst du noch ein zusätliches Dummy Templateparameter reinbaun, was auch bei gleicher Bitzahl unterschiedliche Spezialisierungen erzeugt.

    template< int size,typename DUMMYTYPE, int isSigned >
    struct BitsToType{};
    template< int isSigned >
    struct BitsToType< TypeSize::uint,int, isSigned  >{};
    template< int isSigned >
    struct BitsToType< TypeSize::ull,long, isSigned  >{};
    

    Dann müsste der Benutzer aber einen Typ für Dummytype mitangeben:
    BitsToType< 32, ???, TypeTraits::NoSign >



  • .filmor schrieb:

    Welche, #include <boost/cstdint.hpp> ? 😉

    Du weisst sicherlich, dass wir im ISO C++ Forum sind. Und boost gehört _nicht_ zum Standard. 😉 Zumal werden in <cstdint.hpp> ebenfalls recht viele Präprozessor Anweisungen verwendet. Und C's stdint.h ist dort Voraussetzung, was eine weitere Abhängigkeit bedeutet.

    s| schrieb:

    Dann müsste der Benutzer aber einen Typ für Dummytype mitangeben:
    BitsToType< 32, ???, TypeTraits::NoSign >

    Deshalb halte ich dies für keine gute Idee. So wie ich dich verstanden habe, soll man nur eine Bitgrösse angeben und der Compiler selbst den passenden Typ wählen.

    Da ich dies recht interessant finde, werde ich mir selbst mal ein paar Gedanken machen und mal schauen, ob was Sinnvolles dabei rauskommt.



  • groovemaster schrieb:

    s| schrieb:

    Dann müsste der Benutzer aber einen Typ für Dummytype mitangeben:
    BitsToType< 32, ???, TypeTraits::NoSign >

    Deshalb halte ich dies für keine gute Idee. So wie ich dich verstanden habe, soll man nur eine Bitgrösse angeben und der Compiler selbst den passenden Typ wählen.

    Genau, so hab ich mir das gedacht.


  • Mod

    da wir uns hier offensichtlich abseits der 08/15 char/short/int=8/16/32 bit in 2er komplement bewegen wären noch ein paar weitere dinge zu berücksichtigen.
    immerhin erlaubt der standard padding bits für integrale typen != [signed/unsigned] char. dann stellt sich allerdings die frage, welchen sinn die bitangabe haben soll (oder man erweitert das template auf zwei bit parameter. immerhin macht es einen unterschied, ob ich einen type haben will, der exakt 32bit zur darstellung seines wertes benötigt, oder einen, der genau 32bit im speicher verbraucht). und für vorzecihenbehaftet typen wäre noch die art der darstellung für negative typen interessant.



  • Camper, sowas würd ich gerne machen, aber mir ist kein Weg bekannt wie man das ermitteln kann 😕



  • camper schrieb:

    da wir uns hier offensichtlich abseits der 08/15 char/short/int=8/16/32 bit in 2er komplement bewegen wären noch ein paar weitere dinge zu berücksichtigen.

    Also ich würde schon erstmal eine Verwendung bei den bekannten Bitgrössen sehen. Der einzige Unterschied ist halt, dass CHAR_BIT nicht immer 8 sein muss.

    camper schrieb:

    immerhin erlaubt der standard padding bits für integrale typen != [signed/unsigned] char. dann stellt sich allerdings die frage, welchen sinn die bitangabe haben soll (oder man erweitert das template auf zwei bit parameter. immerhin macht es einen unterschied, ob ich einen type haben will, der exakt 32bit zur darstellung seines wertes benötigt, oder einen, der genau 32bit im speicher verbraucht).

    Ach ja? Was ist denn der Unterschied für dich dabei?
    Solche Gedankenspiele sind für uns doch erstmal vollkommen nebensächlich. Wir haben sizeof, was uns die Grösse in Byte liefert und CHAR_BIT, was uns die Bits pro Byte angibt. Mehr haben wir nicht und mehr brauchen wir auch erstmal nicht.

    camper schrieb:

    und für vorzecihenbehaftet typen wäre noch die art der darstellung für negative typen interessant.

    Nee, nicht im Moment. signed ist vorzeichenbehaftet, unsigned nicht. Mehr brauchen wir erstmal nicht zu wissen.



  • Im Standard (3.9.1) steht, dass die Darstellung von signed und unsigned gleich ist.
    Allerdings habe ich festgestellt, dass long int nur mindestens so groß sein muss wie short, d.h. unter Umständen können long und short gleich groß sein und char und short ebenfalls.
    Da muss ich mir noch was überlegen.



  • So habe das ganze jetzt mit Typliste und jetzt ist es egal, wenn mehrere Typen die gleiche Größe haben.

    Die Größe der Typliste wird hier nicht benötigt, ebenfalls Get und Length. Aber ohne das wäre es ja gar keine echte Typliste 😉
    Die Typliste selbst werde ich wohl mal noch erweitern, derzeit kann sie nur 4Elemente aufnehmen, da ich für die Typen nicht mehr gebraucht habe.

    Hier der Quellcode, die Anwendung ist gleich geblieben.

    template< bool condition, class TypeIfTrue, class TypeIfFalse >
    struct IfThenElse
    {
    	typedef TypeIfFalse Type;
    };
    
    template< class TypeIfTrue, class TypeIfFalse >
    struct IfThenElse< true, TypeIfTrue, TypeIfFalse >
    {
    	typedef TypeIfTrue Type;
    };
    
    struct Empty{};
    
    template< typename T1 = Empty, typename T2 = Empty,
    		  typename T3 = Empty, typename T4 = Empty > struct TypeList;
    
    template< typename T1, typename T2, typename T3, typename T4 >
    struct TypeList
    {
    	typedef T1 Head;
    	typedef TypeList< T2, T3, T4 > Tail;
    	enum
    	{
    		length = Tail::length + 1
    	};
    };
    
    template<>
    struct TypeList< Empty, Empty, Empty, Empty >
    {
    	enum
    	{
    		length = 0
    	};
    };
    
    template< typename TypeList >
    struct Length
    {
    	enum
    	{
    		val = TypeList::length
    	};
    };
    
    template< typename TypeList, int Index, int Depth = 0, 
    		  bool End = ( Index == Depth ),
    		  bool OutOfRange = Length< TypeList >::val == 0 >
    struct Get
    {
    	typedef typename Get< typename TypeList::Tail, Index, Depth + 1 >::Type Type;
    };
    
    template< typename TypeList, int Index, int Depth, bool End >
    struct Get< TypeList, Index, Depth, End, true >
    {
    };
    
    template< typename TypeList, int Index, int Depth, bool OutOfRange >
    struct Get< TypeList, Index, Depth, true, OutOfRange >
    {
    	typedef typename TypeList::Head Type;
    };
    
    #include <climits>
    
    template< typename T >
    struct TypeWrapper
    {
    	typedef T Type;
    	enum
    	{
    		size = sizeof( T ) * CHAR_BIT
    	};
    };
    
    template< typename TypeList, int size, int depth = 0,
    		  bool found = ( size == TypeList::Head::size ) >
    struct Find
    {
    	typedef typename Find< typename TypeList::Tail, size, depth + 1 >::Type Type;
    };
    
    template< typename TypeList, int size, int depth >
    struct Find< TypeList, size, depth, true >
    {
    	typedef typename TypeList::Head::Type Type;
    };
    
    typedef TypeList< TypeWrapper< unsigned char >, 
    				  TypeWrapper< unsigned short >,
    				  TypeWrapper< unsigned long >, 
    				  TypeWrapper< unsigned long long > > UnsignedIntTypes;
    
    typedef TypeList< TypeWrapper< signed char >, 
    				  TypeWrapper< signed short >,
    				  TypeWrapper< signed long >, 
    				  TypeWrapper< signed long long > > SignedIntTypes;
    
    struct TypeTraits
    {
    	enum
    	{
    		NoSign = 0,
    		WithSign = 1
    	};
    };
    
    template< int size, int isSigned >
    struct BitsToType
    {
    	typedef 
    		typename 
    			IfThenElse< isSigned,
    						typename Find< SignedIntTypes, size >::Type,
    						typename Find< UnsignedIntTypes, size >::Type >::Type Type;
    };
    
    //Anwendungsbeispiele:
    //typedef BitsToType< 8, TypeTraits::NoSign >::Type uint8;
    //typedef BitsToType< 16, TypeTraits::WithSign >::Type sint16;
    //typedef BitsToType< 32, TypeTraits::NoSign >::Type uint32;
    //typedef BitsToType< 32, TypeTraits::WithSign >::Type sint32;
    


  • Ach kommt schon sagt was dazu 🙂

    Wenn ihr nichts sagt weil ich keine Kommentare dabei stehen habe und es inzwischen doch etwas größer geworden ist, dann sagt es, die füge ich gerne dazu, wenn ich dafür ne Kritik bekomme 🙂



  • So, hab mir auch mal ein paar Gedanken gemacht und folgendes ist dabei rausgekommen:

    #include <climits>
    #include <cstddef>
    
    // tja, das gute alte if/then/else
    template <bool Condition, class Then, class Else>
    struct if_then_else;
    
    template <class Then, class Else>
    struct if_then_else<false, Then, Else>
    {
    	typedef Else result;
    };
    
    template <class Then, class Else>
    struct if_then_else<true, Then, Else>
    {
    	typedef Then result;
    };
    
    namespace native_integer
    {
    
    // unsere Typliste, mal etwas anders :)
    // leicht erweiterbar
    template <size_t I, bool Signed>
    struct type_list;
    
    template <>
    struct type_list<0, true>
    {
    	typedef signed char type;
    };
    
    template <>
    struct type_list<0, false>
    {
    	typedef unsigned char type;
    };
    
    template <>
    struct type_list<1, true>
    {
    	typedef signed short type;
    };
    
    template <>
    struct type_list<1, false>
    {
    	typedef unsigned short type;
    };
    
    template <>
    struct type_list<2, true>
    {
    	typedef signed int type;
    };
    
    template <>
    struct type_list<2, false>
    {
    	typedef unsigned int type;
    };
    
    template <>
    struct type_list<3, true>
    {
    	typedef signed long type;
    };
    
    template <>
    struct type_list<3, false>
    {
    	typedef unsigned long type;
    };
    
    // hier durchsuchen wir die Typliste nach einem passenden Eintrag
    template <size_t Size, bool Signed, size_t I = 0>
    struct type_selection
    {
    private:
    	typedef typename if_then_else<
    		sizeof(typename type_list<I, Signed>::type) * CHAR_BIT == Size,
    		type_list<I, Signed>,
    		type_selection<Size, Signed, I + 1>
    		>::result result;
    public:
    	typedef typename result::type type;
    };
    
    // Ende der Rekursion
    // private deshalb, damit wir einen Compilefehler bekommen
    template <size_t Size, bool Signed>
    struct type_selection<Size, Signed, 4>
    {
    private:
    	struct type
    	{
    	};
    };
    
    // lediglich dieses Template ist fuer den Anwender von Interesse
    template <size_t Size, bool Signed>
    struct type_definition
    {
    	typedef typename type_selection<Size, Signed>::type result;
    };
    
    } // namespace native_integer
    
    // Verwendung
    typedef native_integer::type_definition<16, false>::result uint16;
    typedef native_integer::type_definition<15, false>::result uint15; // win32: ouch
    

    Ist nur der erste Entwurf und durchaus noch verbesserungsfähig. Die Implementierung ist relativ simpel gehalten und verzichtet auf spezielle Techniken, wie Sequenzen und Iteratoren. Und wer glaubt, sein Compiler hat noch irgendwelche anderen Typen parat (wie zB long long), kann diese relativ einfach nachschieben, indem er eine weitere Spezialisierung für native_integer::type_list hinzufügt und den Index in der native_integer::type_selection Spezialisierung anpasst, um das Ende der Rekursion zu korrigieren. Theoretisch kann man auch die Spezialisierung für native_integer::type_selection auch weglassen, da am Ende der Typliste der Compiler sowieso einen Fehler bringen würde. Um unseren Compiler aber nicht zu sehr zu stressen und um die Fehlermeldungen in Grenzen zu halten, behalten wir die Spezialisierung bei.

    @s|
    Hab deinen Code mal kurz getestet und scheint zu funktionieren, obwohl ich ihn nicht bis ins Detail durchgeschaut habe. Für meinen Geschmack ist er zu kompliziert gestrickt, was aber erstmal egal ist, sofern er funktioniert. Für Lernzwecke ist er jedenfalls durchaus interessant.



  • Dein Ansatz ist nicht so viel anders wie meiner, ich habe die typelist allgemein gehalten und du hast eine speziell für den Zweck geschrieben.

    Finde deinen Ansatz prinzipiell sehr nett, aber es wäre schon, wenn du die maximale Größe automatisch ermitteln würdest, so wird das Ändern leider nen bischen zum Gefrickel.

    Bei mir ist es das derzeit zwar auch, aber ich finde diese Art von Typliste schöner, als die Rekursive, welche man zwangsweise mit nem Makro erstellen muss um den Überblick zu behalten (wird bei Loki verwendet).

    Den Find-Algo habe ich bei mir speziell für den TypeWrapper ausgelegt, das sollte auch noch verallgemeinert werden. BitsToType existiert eigentlich auch nur noch um den Sinn des ganzen zu untermauern, da ein Find leider nicht so aussagekräftig ist.

    Werde mich Morgen wohl hinsetzen und nochmal ne neue (größere) Typliste schreiben und schauen ob ich nen allgemeinen Find-Algo hinbekomme. Deine Idee mit dem überladen der Größen anhand von signed und unsigned gefällt mir, so könnte ich bei mir die zweite Typliste streichen 🙂



  • Ach eines hab ich ganz vergessen, wenn bei dir ein Typ nicht existiert, dann ist die Fehlermeldung weniger verwirrend wie bei meiner Version.
    Das muss ich bei mir auch noch ändern, aber wie gesagt will das ganze eh nochmal frisch machen, mit ner Typliste ist das eigentliche Problem bzw. Vorhaben eh keine Schwierigkeit mehr 🙂



  • Hier ist noch ein anderer Ansatz

    struct Tail{};
    template<class TElement, class TNext>
    struct  List{
    	typedef TElement Element;
    	typedef TNext Next;
    };
    
    typedef List<signed char, List<signed short, List<signed int, List<signed long, Tail> > > > SignedIntegerList;
    typedef List<unsigned char, List<unsigned short, List<unsigned int, List<unsigned long, Tail> > > > UnsignedIntegerList;
    
    template<bool yesno, class TTrue, class TFalse>
    struct IfElse;
    
    template<class TTrue, class TFalse>
    struct IfElse<true, TTrue, TFalse>{
    	typedef TTrue Result;
    };
    
    template<class TTrue, class TFalse>
    struct IfElse<false, TTrue, TFalse>{
    	typedef TFalse Result;
    };
    
    template<int bytes, class TFrom = UnsignedIntegerList>
    struct SelectInteger{
    	typedef
    	  typename IfElse<
    	    sizeof(typename TFrom::Element)==bytes,
    	    typename TFrom::Element,
    	    typename SelectInteger<bytes, typename TFrom::Next>::Result
    	  >::Result Result;
    };
    
    template<int bytes>
    struct SelectInteger<bytes, Tail>{
    	typedef void Result;
    };
    

    Erscheint mir etwas einfacher als die bisherigen Ansätze. Die Fehlermeldung des GCC ist eine Zeile groß in der er ausdrückt, dass er keine "void" Variable anlegen kann. Sollte als Fehlermeldung doch recht einfach zu verstehen sein.



  • Hier das ganz noch einmal in einer überarbeiteten Version die viel allgemeiner gehalten ist. Desweiteren ist sie besser gegliedert. Sollte also leichter verständlich sein.

    //////////////////////////
    //
    //    IfElse
    //
    template<bool yesno, class TTrue, class TFalse>
    struct IfElse;
    
    template<class TTrue, class TFalse>
    struct IfElse<true, TTrue, TFalse>{
    	typedef TTrue Result;
    };
    
    template<class TTrue, class TFalse>
    struct IfElse<false, TTrue, TFalse>{
    	typedef TFalse Result;
    };
    
    //////////////////////////
    //
    //    TypeList
    //
    struct TypeListTail{};
    template<class TElement, class TNext>
    struct  TypeList{
    	typedef TElement Element;
    	typedef TNext Next;
    };
    
    //////////////////////////
    //
    //    Default integer type lists
    //
    typedef TypeList<signed char, TypeList<signed short, TypeList<signed int, TypeList<signed long, TypeListTail> > > > SignedIntegerList;
    typedef TypeList<unsigned char, TypeList<unsigned short, TypeList<unsigned int, TypeList<unsigned long, TypeListTail> > > > UnsignedIntegerList;
    
    //////////////////////////
    //
    //    Select Type
    //
    template<class TCondition, class TFrom>
    struct SelectType{
    	typedef
    	  typename IfElse<
    	    TCondition::template Evaluate<typename TFrom::Element>::result,
    	    typename TFrom::Element,
    	    typename SelectType<TCondition, typename TFrom::Next>::Result
    	  >::Result Result;
    };
    
    template<class TCondition>
    struct SelectType<TCondition, TypeListTail>{
    	typedef void Result;
    };
    
    //////////////////////////
    //
    //    Size condition
    //
    template<int bytes>
    struct SizeCondition
    {
    	template<class T>
    	struct Evaluate{
    		static const bool result = (sizeof(T) == bytes);
    	};
    };
    
    //////////////////////////
    //
    //    TypeListUnion
    //
    template<class TTypeListA, class TTypeListB>
    struct TypeListUnion
    {
    	typedef typename TypeListUnion<
    	  typename TTypeListA::Next,
    	  TypeList<typename TTypeListA::Element, TTypeListB>
    	>::Result Result;
    };
    
    template<class TTypeListB>
    struct TypeListUnion<TypeListTail, TTypeListB>
    {
    	typedef TTypeListB Result;
    };
    
    //////////////////////////
    //
    //    SizeInteger
    //
    struct SelectIntegerSign{
    	enum{
    		unimportant,
    		no_sign,
    		sign
    	};
    };
    
    template<int bytes, int has_sign = SelectIntegerSign::unimportant>
    struct SelectInteger;
    
    template<int bytes>
    struct SelectInteger<bytes, SelectIntegerSign::no_sign>
    {
    	typedef typename SelectType<SizeCondition<bytes>, UnsignedIntegerList>::Result Result;
    };
    
    template<int bytes>
    struct SelectInteger<bytes, SelectIntegerSign::sign>
    {
    	typedef typename SelectType<SizeCondition<bytes>, SignedIntegerList>::Result Result;
    };
    
    template<int bytes>
    struct SelectInteger<bytes, SelectIntegerSign::unimportant>
    {
    	typedef typename SelectType<
    	  SizeCondition<bytes>,
    	  typename TypeListUnion<UnsignedIntegerList, SignedIntegerList>::Result
    	>::Result Result;
    };
    
    //////////////////////////
    //
    //    Example
    //
    int main()
    {
    	SelectInteger<1>::Result a;
    	SelectInteger<2, SelectIntegerSign::sign>::Result b;
    	SelectInteger<4, SelectIntegerSign::no_sign>::Result c;
    
    	typedef TypeList<unsigned long long, UnsignedIntegerList> UnsignedIntegerListWithLL;
    	SelectType<SizeCondition<8>, UnsignedIntegerListWithLL>d;
    }
    


  • Ben04 schrieb:

    Die Fehlermeldung des GCC ist eine Zeile groß in der er ausdrückt, dass er keine "void" Variable anlegen kann. Sollte als Fehlermeldung doch recht einfach zu verstehen sein.

    Mach es trotzdem so wie ich, nimm eine leere Struktur und mach sie privat. Du solltest nämlich nicht vergessen, dass es da noch void* gibt.

    Zudem versteh ich nicht ganz, wozu dein SelectIntegerSign::unimportant gut sein soll. Wenn ich das richtig sehe, sind solche Typen doch immer unsigned.


Anmelden zum Antworten