Generische Character/String Literals in Template Funktion



  • 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!



  • Gern Geschehen 😉

    Die größte Schwäche von C++ ist mittlerweile, dass es zu viele Wege gibt etwas umzusetzen. C++11, C++14, C++17 und nun C++20 haben jeweils deutliche Änderungen für die Template Metaprogrammierung gebracht.


Anmelden zum Antworten