Variadic template funktioniert nicht wie gedacht



  • @Finnegan sagte in Variadic template funktioniert nicht wie gedacht:

    @Miq sagte in Variadic template funktioniert nicht wie gedacht:

    // First arg is uint8_t
      template <uint8_t, class... Args> uint16_t add(uint8_t v, Args... args) {
        return add(v) + add(args...);
      }
    

    Du hast hier einen namenlosen Non-Type Template-Parameter vom Typ uint8_t. Das müsste man eigentlich mit add<5, ...>() oder sowas instanzieren. Ist das wirklich, was du hier vorhast? Ich denke du willst hier wohl eher nur ein template <class... Args> haben. Dass der erste Parameter ein uint8_t ist, legst du ja hier schon über den ersten Funktionsparameter fest.

    Ja, da hast Du natürlich recht - hatte ich inzwischen auch bemerkt, den ersten Parameter entfernt, bekomme aber jetzt das Problem, dass der Compiler nicht die explizite Überladung uint16_t add(uint16_t count, uint8_t *arrayOfBytes); benutzen will, sondern auch da ein Template - was dann scheitert. 😟

    error: invalid operands of types 'unsigned char*' and 'int' to binary 'operator>>'
             MM_data[MM_index++] = (v >> (sz << 3)) & 0xFF;
    


  • @wob sagte in Variadic template funktioniert nicht wie gedacht:

    return (add_impl(args) + ... + 0);

    Das klappt so aber nicht, oder? Ich bin in C++11 unterwegs - kam das später?





  • @Miq sagte in Variadic template funktioniert nicht wie gedacht:

    @wob sagte in Variadic template funktioniert nicht wie gedacht:

    return (add_impl(args) + ... + 0);

    Das klappt so aber nicht, oder? Ich bin in C++11 unterwegs - kam das später?

    Ich glaube das ist eine C++17 Fold Expression.



  • @Finnegan sagte in Variadic template funktioniert nicht wie gedacht:

    @Miq sagte in Variadic template funktioniert nicht wie gedacht:

    @wob sagte in Variadic template funktioniert nicht wie gedacht:

    return (add_impl(args) + ... + 0);

    Das klappt so aber nicht, oder? Ich bin in C++11 unterwegs - kam das später?

    Ich glaube das ist eine C++17 Fold Expression.

    Okay, dann kann ich das nicht nutzen. Mein Zielsystem kann maximal C++11.



  • @Miq sagte in Variadic template funktioniert nicht wie gedacht:

    Ja, da hast Du natürlich recht - hatte ich inzwischen auch bemerkt, den ersten Parameter entfernt, bekomme aber jetzt das Problem, dass der Compiler nicht die explizite Überladung uint16_t add(uint16_t count, uint8_t *arrayOfBytes); benutzen will, sondern auch da ein Template - was dann scheitert. 😟

    error: invalid operands of types 'unsigned char*' and 'int' to binary 'operator>>'
             MM_data[MM_index++] = (v >> (sz << 3)) & 0xFF;
    

    Das ist vielleicht noch nicht die beste Lösung, aber du könntest die Template-Funktionen mit enable_if und is_integral/is_pointer gezielt aktivieren/deaktivieren, so dass der Pointer nicht mehr greift.

    Oder in C++20: template <std::integral... Args>.



  • @Finnegan
    Da komme ich nicht raus aus dem Loch:

    
    template <class T,
              class = typename std::enable_if<std::is_integral<T>::value>::type, class... Args>
      uint16_t add(T v, Args... args) {
        return add(v) + add(args...);
    

    führt auch zu

    ...: In instantiation of 'uint16_t xxx::add(T) [with T = unsigned char*; uint16_t = short unsigned int]':
    ...:   required from 'uint16_t xxx::add(T, Args ...) [with T = unsigned int; <template-parameter-1-2> = void; Args = {unsigned char*}; uint16_t 
    = short unsigned int]'
    ...:   required from here
    ...: error: invalid operands of types 'unsigned char*' and 'int' to binary 'operator>>'
             MM_data[MM_index++] = (v >> (sz << 3)) & 0xFF;
                                      ^
    


  • Kannst du es nicht so machen:

    #include <stdint.h>
    
    uint16_t add(uint16_t count, uint8_t *arrayOfBytes) {
        return 42;
    }
    
    template <typename T> uint16_t add_impl(T v) {
        return v;
    }
    
    uint16_t add() { return 0; }
    
    template <class T1, class... Args> uint16_t add(T1 head, Args... tail) {
        return add_impl(head) + add(tail...);
    }
    
    int main() {
      //return add((uint8_t)1, 3, 5);
       //return add();
       return add(uint16_t(1234), (uint8_t*) nullptr);
    }
    


  • @wob sagte in Variadic template funktioniert nicht wie gedacht:

    template <class T1, class... Args> uint16_t add(T1 head, Args... tail) {
    return add_impl(head) + add(tail...);
    }

    Nope, das läuft auf das gleiche Problem hinaus:

    ...: In instantiation of 'uint16_t xxx::add_impl(T) [with T = unsigned char*; uint16_t = short unsigned int]':
    ...:   recursively required from 'uint16_t xxx::add(T1, Args ...) [with T1 = unsigned char*; Args = {}; uint16_t = short unsigned int]'
    ...:   required from 'uint16_t xxx::add(T1, Args ...) [with T1 = unsigned int; Args = {unsigned char*}; uint16_t = short unsigned int]'
    ...:   required from here
    ...: error: invalid operands of types 'unsigned char*' and 'int' to binary 'operator>>'
             MM_data[MM_index++] = (v >> (sz << 3)) & 0xFF;
                                      ^
    

    Der Compiler erkennt die explizit überladene Funktion nicht.

    Ich werde mir jetzt so helfen, dass ich die überladene Funktion anders nenne, also keine Überladung mehr mache.



  • Das verstehe ich aber nicht, denn mein Code funktioniert doch?!
    Zur Sicherheit hier der Link: https://gcc.godbolt.org/z/ne3xz7 - wie du siehst, kompiliere ich das mit C++11 und es werden 0, 1 und 3 Parameter sowie deine Spezialfunktion mit Pointer aufgerufen.



  • @wob
    Da sind wir schon zwei - ich verstehe es auch nicht. Es ist klar, mit der rekursiven template-Funktion kann der Compiler den Sonderfall signaturmäßig abbilden - aber warum sollte er das tun, wenn es eine explizite Funktion bereits gibt?

    Liegt vielleicht an der Entwicklungsumgebung: PlatformIO für ESP32 (benutzt clang, soviel ich weiß).

    Ich lasse jedenfalls jetzt die Pfoten davon - danke Euch für Eure Hilfe! 👍



  • @Miq Du könntest konkreten Code zeigen. Hilft nicht nur Dir sondern auch anderen.



  • @Miq sagte in Variadic template funktioniert nicht wie gedacht:

    ...: error: invalid operands of types 'unsigned char*' and 'int' to binary 'operator>>'
             MM_data[MM_index++] = (v >> (sz << 3)) & 0xFF;
                                      ^
    

    Lustigerweise habe ich das Problem nicht beim Testen. Da nimmt er auch ohne enable_if im Pointer/count-Fall die entsprechende Funktion. MSVC warnt hier allerdings im Gegensatz zu GCC/Clang (selbst mit -Wall -Wextra) bei den add-Templates: warning C4717: 'add<>': recursive on all control paths, function will cause runtime stack overflow.

    Und tatsächlich, beim näherem Hinschauen:

    template <class... Args> uint16_t add(uint8_t v, Args... args) {
        return add(v) + add(args...);
    }
    

    Diese Funktion als add(v) ohne weitere Argumente aufgerufen, ruft sich links vom + nochmal selbst mit exakt den selben Argumenten auf. Hier habe ich es stattdessen mal mit einem schnellen Fix via std::enable_if<(sizeof...(Args) > 0)> probiert, was auch zu funktionieren scheint:

    https://ideone.com/i0ENy7

    Bleibt nur die Frage, ob dein Compiler das auch checkt. Der scheint da ja ein paar komische Dinge zu machen, wenn ich mir die Probleme mit deinem Code und dem Code @wob so ansehe.



  • @Finnegan
    Danke schön nochmal.

    Mein Originalcode ist sehr umfangreich, der Schnipsel oben befindet sich in einer abstrakten Basisklasse, die Anwendung ist dann im Code einer zweiten Ableitung dieser Basisklasse. Das möchte ich Euch nicht zumuten.

    Ich baue morgen mal ein Testprogramm, das nur die wesentlichen Elemente enthält - aber auf meinem Zielsystem. Dann sehe ich wenigstens, ob das Problem an meinem Drumherum oder am Compiler liegt.



  • @Miq Habe mir das gerade nochmal angesehen. Bin mir nicht ganz sicher, warum du die add-Spezialisierungen für uint8_t, uint16_t, etc. machst, wenn du die daraus gewonnene Information in add(T) wieder wegwirfst. Der Ansatz von @wob macht eigentlich auch das was du willst, ohne die Funktion für jeden Typen spezialisieren zu müssen.

    Der Fehler ...: error: invalid operands of types 'unsigned char*' and 'int' to binary 'operator>>' MM_data[MM_index++] = (v >> (sz << 3)) & 0xFF; rührte übrigens möglicherweise daher, dass du die Funktion für das Byte-Array als add(3, arrayOfBytes) aufgerufen hast. Das Literal 3 hat den Typ int, die Funktion add(uint16_t count, uint8_t*) erwartet aber einen uint16_t, was eine Konvertierung notwendig macht. Durch diese Konvertierung wird diese Funktion bei der Overload-Auflösung jedoch ein schlechterer Kandidat als z.B. add(T v, Args... args). Entweder übergibt man hier explizit einen uint16_t oder man schliesst die Pointer-Argumente beim templatisierten add aus (enable_if), so dass nur die arrayOfBytes-Funktion als Kandidat übrig bleibt.

    Der Ansatz von @wob hat übrigens auch nicht das Problem mit der Endlos-Rekursion, da dein template <class T> uint16_t add(T v) als nicht-variadisches Template bevorzugt wird, auch wenn es ansonsten gleichwertig mit template <class T, class... Args> add(T v, Args... args) wäre. Vorher, mit template <class... Args> uint16_t add(uint8_t v, Args... args) war das nicht der Fall, da dieses spezialisierter als template <class T> uint16_t add(T v) ist (exakter Typ für v, ohne Konvertierung).

    Kurzum, so funkionierts auch - Ansatz von @wob bei dem ich mit enable_if Pointer-Argumente ausschliesse, damit in dem Fall der arrayOfBytes-Overload gewählt wird:

    uint16_t add(uint8_t *arrayOfBytes, uint16_t count);
    ...
    template <class T, class... Args> 
    typename std::enable_if<!std::is_pointer<T>::value, uint16_t>::type
    add(T v, Args... args) {
        return add(v) + add(args...);
    }
    

    https://ideone.com/uRScg6

    Damit das enable_if einfacher zu formulieren ist, habe ich auch die Parameter-Reihenfolge des arrayOfBytes-add vertauscht. Diese Reihenfolge ist ohnehin die verbreitetere, daher würde ich die sowieso empfehlen.



  • @Finnegan sagte in Variadic template funktioniert nicht wie gedacht:

    template <class T, class... Args>
    typename std::enable_if<!std::is_pointer<T>::value, uint16_t>::type
    add(T v, Args... args) {
    return add(v) + add(args...);
    }

    Herzlichen Dank dafür! Die Erklärung mit der Umwandlung nach int leuchtet mir ein, und das Vertauschen der Argumente beim Spezial-add() ist sehr sinnvoll, weil es die template-Ermittlung vereinfacht.

    Läuft auch bei mir auf dem ESP32! 👍



  • @Miq sagte in Variadic template funktioniert nicht wie gedacht:

    @Finnegan sagte in Variadic template funktioniert nicht wie gedacht:
    [...] und das Vertauschen der Argumente beim Spezial-add() ist sehr sinnvoll, weil es die template-Ermittlung vereinfacht.

    Das kann man hier noch so machen, aber es gibt auch Situationen, in denen sich die Funtionen nicht mehr eindeutig über die Parameter-Typen unterscheiden lassen. Spätestens dann macht es Sinn, einen weiteren Funtionsnamen einzuführen (add_array oder sowas). Das ist ne Alternative, wenns noch ärger wird 😉


Anmelden zum Antworten