Generische Character/String Literals in Template Funktion



  • Hi, ich bin mal wieder am Kotzen, weil die einfachsten Dinge unendlich umständlich sind mit C++.

    Folgende Funktion...

    void Type::Parse(const std::string& str)
    {
        char c1 = 'Z';
        std::string str1 = "Test";
    }
    

    ...soll nun abhängig vom einem generischen Character Typ TCHAR implementiert werden:

    template <typename TCHAR>
    void Type::Parse(const std::basic_string<TCHAR>& str)
    {
        TCHAR c1 = _T('Z');
        std::basic_string<TCHAR> str1 = _T("Test");
    }
    

    Problem. Das "_T()" Macro hängt ja nun nicht vom TCHAR Typ ab, sondern von den Projekteinstellungen.
    Darum war meine Idee ein neues "Macro" oder Template-Funktion oder Template-Klasse "_G()" oder oder oder zu implementieren welche
    im Idealfall per Template-Argument-Deduktion wie folgt verwendet werden kann:

    template <typename TCHAR>
    void Type::Parse(const std::basic_string<TCHAR>& str)
    {
        TCHAR c1 = _G('Z');
        std::basic_string<TCHAR> str1 = _G("Test");
    }
    

    oder auch

    template <typename TCHAR>
    void Type::Parse(const std::basic_string<TCHAR>& str)
    {
        TCHAR c1 = _G<TCHAR>('Z');
        std::basic_string<TCHAR> str1 = _G<TCHAR>("Test");
    }
    

    Aber ich bekomme es nicht hin. Ein Versuch war z.B. in etwa wie folgt:

    template <typename TCHAR, typename VALUE>
    constexpr TCHAR _G(VALUE value)
    {
    	if constexpr (std::is_same<char, TCHAR>::value)
    	{
    		return _A(value);
    	}
            if constexpr (std::is_same<wchar_t, TCHAR>::value)
    	{
    		return _W(value);
    	}
            ....
    }
    

    Aber das scheitert daran, dass das "_W" Macro (#define _W(X) L##X) in "Lvalue" aufgelöst wird.
    Außerdem ist VALUE zu diesem Zeitpunkt eh schon vom falschen Typ.
    Man muss wohl denke ich mit einem Macro kombinieren usw.
    Jemand eine klevere Idee?



  • Gegen wie viele strings ("Test") musst du denn vergleichen? Ist das nur einer oder eine kleine, aber feste Anzahl? Oder können das beliebig viele sein? Und falls beliebig viele, wo kommen die her?
    Edit:
    Das kleinste Problem ist die Initialsierung von c1, das geht mit der uniform initialisation : TCHAR c1{'Z'}.



  • Hi, TCHAR kann sowohl char, wchar_t bzw. char8_t, char16_t char32_t sein. Die Funktionen sind auch sehr lang weshalb ich duplizierten Code gerne vermeiden wöllte.



  • @Enumerator Nur zu Verständnis habe ich mir mal angeschaut, wie _T überhaupt definiert ist. Das Makto findet sich in tchar.h und eingedampft aufs Essentielle sieht das darin so aus:

    #ifdef _UNICODE
        #define __T(x)      L ## x
    #else
        #define __T(x)      x
    #endif
    
    #define _T(x)       __T(x)
    #define _TEXT(x)    __T(x)
    

    Das einzige, was dieses Makro macht, ist aus einem C-String-Literal ein wchar_t-basiertes String-Literal zu machen, falls _UNICODE definiert ist.

    Das Problem, das ich hier sehe, wenn man das ohne Makro lösen will, ist, dass der nackte Ausdruck "Test" ein char*-Literal ist, das man erstmal in wchar_ts umwandeln muss, wenn man damit einen wchar_t* initialisieren will. Das selbe gilt natürlich auch umgekehrt, wenn stattdessen _G(L"Test") verwendet wird.

    Ich befürchte, da kommt man um eine Konvertierung im Code nicht herum, obwohl das Problem auf en ersten Blick so banal aussieht. Ich weiss nicht, ob schonmal jemand eine constexpr-UTF8/UTF16-Konvertierung implementiert hat, das wäre hier eine nützliche Sache.

    Alternativ natürlich via Konvertierung zur Laufzeit. Z.B. Code und char-basierte String-Literale in UTF-8 und wenn wchar_t gefragt ist, den String in einem if constexpr-Block zuerst nach UTF-16 konvertieren (oder eben umgekehrt).

    Sorry, was besseres weiss ich auch nicht. Hoffe für dich, dass ich irgendeinen coolen Trick übersehen habe 😉



  • @Enumerator sagte in Generische Character/String Literals in Template Funktion:

    Hi, TCHAR kann sowohl char, wchar_t bzw. char8_t, char16_t char32_t sein. Die Funktionen sind auch sehr lang weshalb ich duplizierten Code gerne vermeiden wöllte.

    Das wollte ich garnicht wissen, ich wollte nur wissen, wo die str1 Variable herkommt. Vielleicht kann man da was mit type traits bauen.



  • Einfach an https://utf8everywhere.org/#windows halten - do not use the _T macro.



  • Welcher C++ Standard? 98/03/11/13/17/20?



  • Also ab C++11 sollte sowas in der Richtung gehen:

    #include <array>
    #include <iostream>
    #include <stddef.h>
    #include <string>
    #if __cpp_lib_string_view >= 201606
    #include <string_view>
    #endif
    
    template <class Ch, size_t N>
    struct StrLit {
    #if __cpp_constexpr >= 201304
    constexpr
    #endif
         StrLit(char const* s, size_t n) : size{n - 1} {
            for (size_t i = 0; i < n; i++)
                data[i] = s[i];
        }
    
        std::basic_string<Ch> str() const {
            return {data.data(), size};
        }
    
    #if __cpp_lib_string_view >= 201606
    #if __cpp_constexpr >= 201304
    constexpr
    #endif
        std::basic_string_view<Ch> sv() const {
            return {data.data(), size};
        }
    #endif
    
    #if __cpp_constexpr >= 201304
    constexpr
    #endif
        Ch const* ptr() const {
            return &data[0];
        }
    
        std::array<Ch, N> data;
        size_t size;
    };
    
    template <class Ch, size_t N>
    constexpr StrLit<Ch, N> MakeStrLit(char const(&arr)[N]) {
        return StrLit<Ch, N>{&arr[0], N};
    }
    
    #define _SX(Ty_, S_) \
        ([] { \
            using Ty = Ty_; \
            static constexpr char const S[] = S_; \
            return MakeStrLit<Ty>(S); \
        }())
    
    #define _S(Ty_, S_) _SX(Ty_, S_).str()
    #define _SP(Ty_, S_) _SX(Ty_, S_).ptr()
    #define _SV(Ty_, S_) _SX(Ty_, S_).sv()
    
    // ----------------
    
    template <class Ch>
    void print(std::basic_string<Ch> const& s) {
        for (auto const ch : s)
            std::cout << static_cast<char>(ch);
        std::cout << '\n';
    }
    
    #if __cpp_lib_string_view >= 201606
    template <class Ch>
    void print2(std::basic_string_view<Ch> sv) {
        for (auto const ch : sv)
            std::cout << static_cast<char>(ch);
        std::cout << '\n';
    }
    #endif
    
    template <class Ch>
    void print3(Ch const* s) {
        for (; *s; s++)
            std::cout << static_cast<char>(*s);
        std::cout << '\n';
    }
    
    template <class Ch>
    void MyFun() {
        print(_S(Ch, "foo"));
    #if __cpp_lib_string_view >= 201606
        print2(_SV(Ch, "the quick brown string view"));
    #endif
        print3(_SP(Ch, "the quick brown fox lala blabla"));
    }
    
    int main() {
        MyFun<char>();
        MyFun<char16_t>();
        auto const s32 = _S(char32_t, "rarurick");
        print(s32);
    }
    

    Sicher nicht die schönste Implementierung, aber wichtig ist ja nur dass die Makros halbwegs schön zu verwenden sind, und das sollte denke ich passen. Alles andere kann man dann immer später noch verschönern.



  • Bzw. wenn es ausreichend kurze und ausreichend wenige Strings sind natürlich auch einfach

    #include <iostream>
    #include <string>
    
    template <class Ch>
    void print(std::basic_string<Ch> const& s) {
        for (auto const ch : s)
            std::cout << static_cast<char>(ch);
        std::cout << '\n';
    }
    
    template <class Ch>
    void MyFun() {
        std::basic_string<Ch> str{'T', 'e', 's', 't'};
        print(str);
    }
    
    int main() {
        MyFun<char>();
        MyFun<char16_t>();
    }
    


  • Und wenn dich die dynamische Allokation jedes mal nicht stört, dann geht auch

    #include <iostream>
    #include <string>
    #include <string.h>
    
    template <class Ch>
    std::basic_string<Ch> _G(char const* s) {
        const size_t size = strlen(s);
        std::basic_string<Ch> str;
        str.reserve(size);
        for (size_t i = 0; i < size; i++)
            str.push_back(s[i]);
        return str;
    }
    
    template <class Ch>
    void print(std::basic_string<Ch> const& s) {
        for (auto const ch : s)
            std::cout << static_cast<char>(ch);
        std::cout << '\n';
    }
    
    template <class Ch>
    void MyFun() {
        print(_G<Ch>("Test"));
    }
    
    int main() {
        MyFun<char>();
        MyFun<char16_t>();
    }
    


  • Funktioniert natürlich alles nur für ASCII String Literals als Input und UTF-Varianten bzw. ASCII als Output. Wenn du mehr brauchst, dann wird's komplizierter.

    Und jetzt ware ich darauf dass @Columbo kommt und uns zeigt wie das ganze 3x besser und 3x kürzer geht 🙂



  • Der Ansatz mit der constexpr class scheint mir der brauchbarste. Interessant, dass nun auch for Loops in constexpr Kontruktoren/Funktionen funktionieren. War noch auf dem Stand, dass man sich diese durch eine rekursive Funktion emulieren muss. Jetzt würde ich das Tempalte aber gerne noch für sowohl Characters als auch String Literals spezialisieren. Ich scheitere aber schon an der Spezialisierung für Character:

    Spezialisierung:

    template <class CHARTYPE, class BASETYPE, size_t N>
    class _Literal { };
    
    template <class CHARTYPE>
    class _Literal<CHARTYPE, char, 1>
    {
    public:
    	constexpr _Literal(char cValue) :
    		m_cValue(cValue)
    	{
    	}
    
    	constexpr operator CHARTYPE()
    	{
    		return m_cValue;
    	}
    
    private:
    	CHARTYPE m_cValue;
    };
    

    Fehler:

    error C2976: '_Literal': too few template arguments
    error C2784: could not deduce template argument for _Literal<CHARTYPE,BASETYPE,N>' from 'char'
    
    template <class CHARTYPE>
    void Parse(const std::basic_string<CHARTYPE> sText)
    {
    	CHARTYPE c = _Literal<CHARTYPE>('A');
    

    Warum erkennt der Compiler die Spezialisierung nicht?



  • @Enumerator
    Wie wäre es damit?

    #include <cstdio>
    #include <string>
    
    template <class T>
    class _Literal
    {
    public:
      constexpr _Literal(char cValue) :
        m_cValue(cValue),
        m_InitSize(sizeof(cValue)),
        m_Size(sizeof(T))
      {
      }
    
      constexpr operator T()
      {
        return m_cValue;
      }
    
      private:
        T m_cValue;
        const size_t m_InitSize;
        const size_t m_Size;    
    };
    
    
    template <class T>
    void Parse(const std::basic_string<T> sText)
    {
    	T c = _Literal<T>('A');
    }
    
    
    int main()
    {
        Parse(std::wstring(L"Hallo"));
        return 0;
    }
    


  • @Enumerator sagte in Generische Character/String Literals in Template Funktion:

    template <class CHARTYPE>
    class _Literal<CHARTYPE, char, 1>

    Ich verstehe diese Definition deines Templates nicht, sondern hätte eher so etwas erwartet:

    #include <cstdio>
    #include <string>
    
    template <typename T>
    void Foo(T a)
    {
        printf("Basisfunktion\n");
    }
    
    template <>
    inline void Foo(char a)
    {
        printf("char\n");
    }
    
    template <>
    inline void Foo(wchar_t a)
    {
        printf("wchar_t\n");
    }
    
    
    
    int main()
    {
        Foo('a');
        Foo(L'a');
        Foo("Hallo");
        return 0;
    }
    


  • @Quiche-Lorraine sagte in Generische Character/String Literals in Template Funktion:

    @Enumerator sagte in Generische Character/String Literals in Template Funktion:

    template <class CHARTYPE>
    class _Literal<CHARTYPE, char, 1>

    Ich verstehe diese Definition deines Templates nicht, sondern hätte eher so etwas erwartet:

    Was Du machst ist Template Spezialisierung, d.h. man muss jedesmal die komplette Funktion spezialisieren. Wenn man stattdessen mit Template Metaprogramming arbeitet is_same, dann kann man nur bestimmte Teile der Funktion unterschiedlich ausführen. Das spart einiges an Code ein.



  • @Quiche-Lorraine sagte in Generische Character/String Literals in Template Funktion:

    template <class T>
    void Parse(const std::basic_string<T> sText)
    {
    	T c = _Literal<T>('A');
    }
    

    Wie wär's damit?

    template <class T>
    void Parse(const std::basic_string<T> sText)
    {
    	T const c{'A'};
    }
    

    Bzw. wenn du non-ASCII source execution character sets unterstützen willst, dann halt

    	T const c{U'A'};
    


  • Nana, ich will explicit das Template

    template <class CHARTYPE, class BASETYPE>
    class _Literal { };
    

    mit zwei Partial-Spezialisierungen "überschreiben".
    Damit das möglich ist muss der Compiler diese Spezialisierungen aber auch unterscheiden können.
    Dazu die beiden CHARTYPE und BASETYPE Parameter.
    Die String-Literal Spezialisierung habe ich nur noch nicht hier gepostet
    weil ja schon das Character-Literal nicht funktioniert.

    Im Fall von einem Character-Literal ist BASETYPE = char spezialisiert:

    class _Literal<CHARTYPE, char>
    

    Im Fall des String-Literals ist BASETYPE = const char* spezialisiert:

    class _Literal<CHARTYPE, const char*>
    

    Das sollte auch so funktionieren. Kann man hier nachlesen:
    https://en.cppreference.com/w/cpp/language/partial_specialization
    Erstes Beispiel auf der Seite. Den size_t Parameter konnte ich sogar noch eliminieren,
    weil nun auch der std::string den ich anstatt des std::array hernehme nun auch constexpr kann.

    template <class CHARTYPE, class BASETYPE>
    class _Literal { };
    
    template <class CHARTYPE>
    class _Literal<CHARTYPE, char>
    {
    public:
    	constexpr _Literal(char cValue) :
    		m_cValue(cValue)
    	{
    	}
    
    	constexpr operator CHARTYPE()
    	{
    		return m_cValue;
    	}
    
    private:
    	CHARTYPE m_cValue;
    };
    


  • Das Template sollte schon passen, du instanzierst es bloss falsch. Bei der Instanzierung musst du halt alle Template-Parameter angeben. Oder "deduction guides" schreiben. Nur weiss ich nicht ob man "partielle deduction guides" schreiben kann, also welche wo man einen Teil der Parameter noch manuell angeben muss. Glaub nicht, ich wüsste zumindest nicht wie die Syntax dafür aussehen sollte.

    Ich verstehe aber nicht wozu du überhaupt BASETYPE als Template-Parameter brauchst. Mach einfach unterschiedliche Templates für Character-Literale und String-Literale. Und wenn du dann immer noch verschiedene BASETYPE brauchst, dann mach einen template-ctor im Template.

    Also

    template <class CHARTYPE>
    class CharacterLiteral
    {
    public:
    	template <class BASETYPE>
    	constexpr CharacterLiteral(BASETYPE cValue) :
    		m_cValue(cValue)
    	{
    	}
    ...
    

    Und gewöhn dir Namen ala _Literal ab. Alles was mit _ gefolgt von einem Grossbuchstaben anfängt ist ein C++ ein reservierter Bezeichner. Im "global namespace" auch Namen die mit _ gefolgt von einem Kleinbuchstaben anfangen.


    Eine andere Möglichkeit wäre überladene Hilfsfunktionen zu verwenden um deine _Literals zu erzeugen. Dann könntest du das mit der partiellen Spezialisierung beibehalten. Wobei ich aber keinen Grund dafür sehe.



  • @Enumerator sagte in Generische Character/String Literals in Template Funktion:

    Den size_t Parameter konnte ich sogar noch eliminieren,
    weil nun auch der std::string den ich anstatt des std::array hernehme nun auch constexpr kann.

    Nicht ganz. std::string mit constexpr geht nur innerhalb eines constexpr Kontext.



  • @john-0
    👍 Ich könnt dich knutschen.

    Ich habe das std::is_same ausprobiert, mit if constexpr kombiniert und siehe da, auf einmal sind cirka 38 C-Style Funktionen überflüssig geworden!


Anmelden zum Antworten