template spezialisierung



  • hallo - wieso wird hier nicht die richtige spezialisierung aufgerufen (und gibts dazu vll nen workaround? bisher hab ich als quick and dirty lösung einfach return T(from) in der allg. fkt stehen, aber so bald man iwas aufruft, was nicht spezialisiert ist, kommen dann halt noch unverständlichere fehler - außerdem muss das doch gehen!?^^):

    #include <string>
    
    template <typename T, typename U>
    T ConvertString(const U &from);
    
    template <typename T, T> inline
    T ConvertString(const T &from)
    {
        return from;
    }
    
    int main()
    {
        std::string a, b="asd";
        a = ConvertString<std::string>(b);
    //  a = ConvertString<std::string, std::string>(b); <- geht auch nicht
    }
    

    ?
    viel zu sagen gibts dazu glaube ich nicht (außer, dass es auch noch sinnvollere spezialisierungen gibt^^ die funktionieren dann aber so weit)

    es soll - wie der name schon sagt - ein converter zwischen verschiedenen strings werden(versch. frameworks mit versch. strings, wide-strings, normale strings, utf gedöns, ...).
    das ganze soll mit templates passieren, damit man am quellcode an sich nichts mehr ändern muss, nur weil man einen typen ändert...

    MSVC schrieb:

    `

    main.obj : error LNK2019: unresolved external symbol "class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __cdecl ConvertString<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)"

    `

    bb und danke scho mal : >



    1. funktions-templates können nicht partiell spezialisiert werden

    2. so wie du das schreibst ist das einfach ein neues template, welches ein type und ein non-type argument hat. das non-type argument ist dabei vom typ T (und hat keinen namen).

    ich wusste garnicht dass das geht, aber wie man sieht, geht es.

    guckst du:

    #include <iostream>
    
    template <typename T, T K>
    T return_k()
    {
    	return K;
    }
    
    int main(int argc, char* args[])
    {
    	int one = return_k<int, 1>();
    	std::cout << one << "\n";
    
    	int two = return_k<int, 2>();
    	std::cout << two << "\n";
    
    	unsigned long long ll = return_k<unsigned long long, (1ull << 40)>();
    	std::cout << ll << "\n";
    
    	// und das geht halt nicht, weil ein non-type template argument kein UDT sein darf
    //	std::string str = return_k<std::string, "sepp">();
    }
    

    was du zu machen versuchst, macht man normalerweise so:

    template <class TARGET, class SOURCE>
    class converter;
    
    template <class TARGET>
    class converter<TARGET, TARGET>
    {
    public:
    	static TARGET convert(TARGET const& src)
    	{
    		return src;
    	}
    };
    
    template <class TARGET, class SOURCE>
    TARGET convert(SOURCE const& src)
    {
    	return converter<TARGET, SOURCE>::convert(src);
    }
    

    gerade für den anwendungsfall string-converter ist das IMO IDEAL!



  • Gut - jz ists auch klar, wo du das so erläutert hast 😃 Danke 🤡

    namespace detail
    {
    	template <class TARGET, class SOURCE>
    	class converter;
    
    	template <class TARGET>
    	struct converter<TARGET, TARGET>
    	{
    		static TARGET convert(const TARGET &src)
    		{
    			return src;
    		}
    	};
    
    	template <class SOURCE>
    	struct converter<TFrameworkString, SOURCE>
    	{
    		static TFrameworkString convert(const SOURCE &src)
    		{
    			return ConvertToTFrameworkString(src);
    		}
    	};
    }
    
    template <class TARGET, class SOURCE>
    TARGET ConvertString(const SOURCE &src)
    {
    	return detail::converter<TARGET, SOURCE>::convert(src);
    }
    
    template <typename TARGET, std::size_t N>
    TARGET ConvertString(const typename TARGET::value_type (&from)[N])
    {
    	return TARGET(&from[0], &from[N-1]);
    }
    
    template <typename TARGET, typename SOURCE, std::size_t N>
    TARGET ConvertString(const SOURCE (&from)[N])
    {
    	return ConvertString<TARGET>(std::basic_string<SOURCE>(&from[0], &from[N-1]));
    }
    

    Hattest du das so gemeint?

    bb

    edit:

    #include <string>
    
    namespace detail
    {
    	template <class TARGET, class SOURCE>
    	class converter;
    
    	template <class TARGET>
    	struct converter<TARGET, TARGET>
    	{
    		static TARGET convert(TARGET const& src)
    		{
    			return src;
    		}
    	};
    
    	template <class SOURCE>
    	struct converter<std::string, SOURCE>
    	{
    		static std::string convert(const SOURCE &src)
    		{
    			return StdString(src);
    		}
    	};
    }
    
    template <class TARGET, class SOURCE>
    TARGET ConvertString(const SOURCE& src)
    {
    	return detail::converter<TARGET, SOURCE>::convert(src);
    }
    
    int main()
    {
    	std::string a, b;
    	a = ConvertString<std::string>(b);
    }
    

    liefert leider noch immer nen fehler:

    MSVC schrieb:

    more than one partial specialization matches the template argument list

    hab ich irgend nen fehler gemacht? Oo



  • Naja beide Spezialisierungen funktionieren für 2x std::string.
    Dummerweise meint dein Compiler, dass beide gleich "gut" passen, und verweigert daher.

    Die genauen Regeln was "more spezialized" ist und was nicht merke ich mir nie, aber ich denke dein Compiler wird sogar Recht haben in seiner Auslegung.

    Natürlich kann man da auch drum-rum arbeiten, und zwar indem man weitere Hilfs-Klassen-Templates verwendet.

    So inetwa vielleicht:

    #include <string>
    
    namespace detail
    {
        template <class TARGET, class SOURCE>
        class converter2;
    
        // allgemeiner fall
        template <class TARGET, class SOURCE>
        class converter
        {
            static TARGET convert(SOURCE const& src)
            {
                return converter2<TARGET, SOURCE>::convert(src);
            }
        };
    
        // spezialisierung für SOURCE == TARGET
        template <class TARGET>
        struct converter<TARGET, TARGET>
        {
            static TARGET convert(TARGET const& src)
            {
                return src;
            }
        };
    
        // converter2 können wir nun für andere dinge spezialisieren
        template <class SOURCE>
        struct converter2<std::string, SOURCE>
        {
            static std::string convert(const SOURCE &src)
            {
                return StdString(src);
            }
        };
    }
    
    template <class TARGET, class SOURCE>
    TARGET ConvertString(const SOURCE& src)
    {
        return detail::converter<TARGET, SOURCE>::convert(src);
    }
    
    int main()
    {
        std::string a, b;
        a = ConvertString<std::string>(b);
    }
    

    Ich würde es aber eher so machen: Du machst ein Klassen-Template welches dafür zuständig ist den Source-String zu lesen (String-Reader), und ein zweites, welches dafür zuständig ist den Target-String zu bauen (String-Builder).

    Der String-Builder definiert dabei z.B. eine statische Funktion "Build", die selbst eine Template-Funktion ist (Template Argument = Typ des String-Readers).

    Der String-Builder verwendet dann die Funktionen die jeder String-Reader implementieren muss, um die Daten aus dem Source-String rauszuholen, und baut einen String "seines" Typs.

    Das Interface des String-Readers könnte dabei z.B. folgendes sein:
    😉 get_size + get_character (Array-Style)
    😉 get_begin + get_end (Iterator-Style)
    😉 have_next + get_next (Enumerator-Style)

    Im Prinzip sollten alle drei Varianten gut funktionieren, und müssten für die meisten String-Typen auch inetwa gleich schnell sein.

    Für die meisten String-Typen wird wohl die Iterator-Style Variante die am einfachsten zu implementierende sein. Schliesslich bieten sogut wie alle String-Klassen eine .c_str()-artige Funktion an, mit der man einen Zeiger auf den String bekommt, der dann "am Stück" vorliegt. Diesen Zeiger kann man natürlich als Iterator verwenden, auch dann, wenn die Klasse keine eigenen Iteratoren definiert (MFC's CString z.B.).
    Genauso bei C-Strings (char const*) - der Zeiger ist "begin", Zeiger + strlen(Zeiger) ist "end".

    EDIT: wenn du das "Lesen" und das "Bauen" nämlich nicht aufsplittest, dann bekommst du viel zu schnell viel zu viele Fälle (N*M, wenn N=Anzahl der möglichen SOURCE und M=Anzahl der möglichen TARGET).



  • Japp, auch das klingt wieder gut - ich werd mich das WE ma dran versuchen und dann mit meinen (sicherlich vorhandenen^^) Problemen wiederkommen ;o)

    Bis dahin danke schon mal : >

    PS:

    Naja beide Spezialisierungen funktionieren für 2x std::string.
    Dummerweise meint dein Compiler, dass beide gleich "gut" passen, und verweigert daher

    war mir schon klar 😉 das er damit im unrecht sei wollte ich auch nicht sagen - wusste nur nicht, wie ich es am besten schreibe und der compiler-output war ja auch verständlich genug^^



  • also, ich würde so in etwa vorgehen (und hoffe, ich hab nix falsch (oder gar nicht^^) verstanden):

    /*string_convert.h*/
    
    template <class TARGET, class SOURCE>
    Ttarget ConvertString(const SOURCE& src)
    {
      reader<SOURCE> from(src);
      return writer<TARGET>::build(from);
    }
    
    template <class TARGET>
    TARGET ConvertString(const TARGET& src)
    { //sollte ja jetzt gehen, richtig!?
      return src;
    }
    
    template <class TARGET, class SOURCE, std::size_t N>
    Ttarget ConvertString(const SOURCE (&src)[N])
    {
      return ConvertString<TARGET>(std::basic_string<SOURCE>(&src[0], &src[N-1]));
    }
    
    /* helper */
    
    enum interpretation_way
    {
      raw       //ein T <=> ein zeichen, z.Bsp.: basic_string<T>
      , utf_8   //utf- 8
    /*, ...*/
    };
    
    template<typename SOURCE>
    struct Tinterpreting_way;
    
    template<typename T>
    struct Tinterpreting_way < std::basic_string<T> >
    {
    	way(const std::basic_string<T> &) {}
    	enum { interpreting_way = raw };
    };
    
    /* andere spezialisierungen */
    
    /* reader */
    
    template<class SOURCE>
    struct reader : Tinterpreting_way<SOURCE>
    {
      bool is_end() const; //return iter == end
      void next(); //je nach interpretation_way spezialisieren -> Prob 1
      const_pointer get() const; //Prob 2: rückgabewert ist doof, aber was wäre die alternative? nen pair aus iterator und anzahl der zugehörigen zeichen?
    
      reader(const SOURCE &src); //setzt iter und end
    private:
      const_iterator mIterator, mEnd;
    };
    
    /* writer */
    
    template <typename TARGET>
    struct writer : Tinterpreting_way<SOURCE>
    {
      template <typename READER>
      static Ttarget build(READER &src) //prob 4
      {
        TARGET R;
        R.reserve( src.estimate_size() ); //ist es überhaupt sinnvoll, die länge abzuschätzen? falls ja, wie sollte man das anstellen -> Prob. 3
        for( ; !src.is_end(); src.next())
          R.push_back(src.get());
        return R;
      }
    };
    

    siehst du schwächen? würdest du anders vorgehen?
    wenn nicht:
    prob1: nen switch? oder kann man dort iwie spezialisieren? (mit enable_if sollte es ja gehen - auch einfacher?)
    prob2: gefällt mir iwie nicht so richtig, weil der reader dann wieder iwie sinnlos ist, wenn man das auslesen doch wieder von hand machen muss 😣
    prob3: ist ja eher nen problem des readers, aber ich habs ma dort hin geschrieben, weil es ein problem des bauens ist.
    gerade bei relativ kurzen strings muss überdurchschnittlich oft hin- und herkopiert werden(oder ich verschwende rel. viel platz, weil ich zu viel reserve).
    prob 4: oder per value übergeben? 2string-iteratoren werden idR ja nur 2x 1WORD = 1DWORD groß sein. ich hab aber mal gelesen, dass man die übergabe per referenz erst ab ca. 2DWORDs vorziehen sollte. das hier keine const-ref geht, sollte ja klar sein^^ ist also nur die frage ob per value oder per (non-const) ref.

    ich hoffe, du bist ein geduldiger mensch... ;o)

    bb

    PS: in reader ist das mit dem push_back wohl etwas sehr vereinfacht^^ da muss nat. auch noch ne private statische methode hinzukommen, die das zeichen richtig formatiert, was dort hinzugefügt werden soll...



  • *push*
    ich hab noch immer keinen guten weg für reader::get gefunden 😣

    value_type wäre ja eigtl das sinnvollste, aber das geht spätestens bei utf-8 nicht mehr...
    also was würdest du/ihr anstellen?

    bb



  • Achjeh wenn du Encoding-Konvertierung auch noch gleich machen willst, dann wird das alles wesentlich komplizierter.

    Alles jetzt bis ins Detail zu beschreiben würde mir echt zu lange dauern. Grundsätzlich solltest du zusätzlich zum Reader und Writer (Builder) noch zwei weitere Teile einführen, nämlich einen Encoder und einen Decoder.

    In dem Fall würde ich die ganze Kette dann mit "push" betreiben. Also der Reader hat genau eine Funktion, nämlich "PushAll(DownStreamFilter& f)".
    Der DownStreamFilter (Decoder, Encoder und Writer) muss dabei eine Funktion "Push(T t)" implementieren, wobei T der "Zeichentyp" ist mit dem er arbeitet. Ein Decoder für UTF-8 hätte also eine Push(char) Methode, ein Decoder für UTF-16 hätte eine Push(short) Methode, ein Encoder hat grundsätzlich eine Push(int32_t) Methode (Output vom Decoder == Input für den Encoder würde ich ausschliesslich in UCS4 machen), etc.

    Zum Schluss gibt's noch eine "Flush" Methode, die aufgerufen wird, nachdem alle "Zeichen" "gepusht" wurden.

    Der "Writer" hat dabei einen Puffer, in dem er den Target-String erstellt, und eine Funktion, mit dem man den fertigen String "rausmoven" kann (bzw. ohne rvalue Referenzen: einen einfachen Getter).

    Natürlich gibt es dabei auch haufenweise kleinere Fallstricke, wie z.B. dass wchar_t mal 16 Bit ist und mal 32, dass char mal signed ist und mal unsigned etc. Aber wie schon gesagt: es würde ewig dauern das alles zu erklären.

    Lies dich einfach mal zum Thema Unicode und Multi-Byte Encodings schlau. Ist ein grauenhaftes Thema, aber viel Spass damit 😉



  • hustbaer schrieb:

    Zum Schluss gibt's noch eine "Flush" Methode, die aufgerufen wird, nachdem alle "Zeichen" "gepusht" wurden.

    wozu würdest du ein flush vorschlagen?

    bb



  • um implementierungen zu ermöglichen, die auf libraries/APIs zurückgreifen, welche nur auf ganzen strings arbeiten können.

    beispielsweise die windows funktion MultiByteToWideChar.

    der musst du einen ganzen string übergeben, und sie gibt dir einen ganzen string zurück. d.h. wenn die source-codepage multi-byte ist (also bestimmte zeichen mit zwei oder mehr byte kodiert), dann müsste der konverter-filter irgendwie rausbekommen, was solche zwei (oder mehr) byte sequenzen sind. würde er nämlich jedes byte einzeln an MultiByteToWideChar übergeben, würde die funktion einfach fehlschlagen -- ein lead byte alleine kann man ja nicht konvertieren.

    und dass man sich um sowas nicht scheren will ist ja gerade der grund, warum man überhaupt auf eine fertige library/API zurückgreift.

    flush ist vielleicht auch kein idealer name für die funktion, da ein upstream filter sie nicht einfach so aufrufen darf, sondern nur am ende des strings. bzw. wenn sichergestellt ist, dass sie nicht mitten "in" einem zeichen aufgerufen wird.

    mir fällt aber grad kein besserer ein. hm. vielleicht "notify_string_end".



  • ich würd das push und flush(oder wie auch immer) eh nur intern machen und den ctor jedes mal 2 iteratoren bekommen lassen!?
    der kann ja dann x mal push aufrufen und am ende ggf auch noch flush...

    template<typename OUT, typename IN>
    OUT convert(const IN &in)
    {
      reader<IN> rd(in.begin(), in.end());
      decoder<typename reader< IN>::value_type, typename reader< IN>::format_type> decodet(rd.begin(), rd.end());
      encoder<typename writer<OUT>::value_type, typename writer<OUT>::format_type> encodet(decodet.begin(), decodet.end());
      writer<OUT> out(encodet.begin(), encodet.end());
      return out.get();
    }
    

    hab zwar nur angefangen den rest zu implementieren aber bisher sind mir noch keine schwächen aufgefallen!?
    übrigens danke für die geduld ;o)

    bb



  • nö das sieht eigentlich gut aus.


Anmelden zum Antworten