Rekursive Templatefunktion



  • Hi,

    ich sitze vor einer Templatefunktion, die rekursiv eine andere aufruft und Werte aus einer Zeichenkette extrahieren soll. Das tut sie aber nur für den ersten Wert, alle weiteren werden nicht gelesen und ich verstehe einfach nicht warum.

    Programm dazu:

    #include <vector>
    #include <cstdio>
    #include <cstdint>
    
    using std::vector;
    
    vector<uint8_t> MM_data;
    
    // add() - add a single data element MSB first to MM_data. Returns updated size
    template <class T> uint16_t add(T v) {
      uint16_t sz = sizeof(T);    // Size of value to be added
    
      // Copy it MSB first
      while (sz) {
        sz--;
        MM_data.push_back((v >> (sz << 3)) & 0xFF);
      }
      // Return updated size (logical length of message so far)
      return MM_data.size();
    }
    
    // Template function to extend add(A) to add(A, B, C, ...)
    template <class T, class... Args>
    typename std::enable_if<!std::is_pointer<T>::value, uint16_t>::type
    add(T v, Args... args) {
      add(v);
      return add(args...);
    }
    
    // get() - read a MSB-first value starting at byte index. Returns updated index
    template <typename T> uint16_t get(uint16_t index, T& retval) {
      uint16_t sz = sizeof(retval);    // Size of value to be read
    
      retval = 0;                      // return value
    
      // Will it fit?
      if (index <= MM_data.size() - sz) {
        // Yes. Copy it MSB first
        while (sz) {
          sz--;
          retval <<= 8;
          retval |= MM_data[index++];
        }
      }
      return index;
    }
    
    // =============== Hier kommt die Funktion, um die es geht: ==========
    // Template function to extend get(index, A) to get(index, A, B, C, ...)
    template <class T, class... Args>
    typename std::enable_if<!std::is_pointer<T>::value, uint16_t>::type
    get(uint16_t index, T& v, Args... args) {
      uint16_t pos = get(index, v);
      return get(pos, args...);
    }
    
    int main(int argc, char **argv) {
      uint8_t b1 = 23;
      uint8_t b2 = 0;
      uint16_t r1 = 0xEFFE;
      uint16_t r2 = 0;
      uint32_t w1 = 0xDEADBEEF;
      uint32_t w2 = 0;
    
      // Analog zu get() Füllen mit rekursivem add()
      add(b1, r1, w1);
    
      // Ausgeben zur Kontrolle
      for (auto &b: MM_data) {
        printf("%02X ", b);
      }
      printf("\n");
      
      // Der rekursive get()-Aufruf geht nur für die erste Variable, weitere nicht:
      uint16_t pos = get(0, b2, r2, w2);
      printf("pos=%d, b2=%02X, r2=%04X, w2=%08X\n", pos, b2, r2, w2);
    
      // Das hier geht aber:
      pos = get(0, b2);
      pos = get(pos, r2);
      pos = get(pos, w2);
      printf("pos=%d, b2=%02X, r2=%04X, w2=%08X\n", pos, b2, r2, w2);
    
      return 0;
    }
    

    Ausgabe:

    17 EF FE DE AD BE EF 
    pos=7, b2=17, r2=0000, w2=00000000
    pos=7, b2=17, r2=EFFE, w2=DEADBEEF
    

    Wo steckt mein Denkfehler?





  • @Swordfish Danke, probiere ich morgen. Ich hatte befürchtet, dass das wegen des Abspaltens des jeweils ersten Arguments nicht klappen würde.



  • Yup, danke nochmal. Hiermit geht es dann:

    template <class T, class... Args>
    typename std::enable_if<!std::is_pointer<T>::value, uint16_t>::type
    get(uint16_t index, T& v, Args&&... args) {
      uint16_t pos = get(index, v);
      return get(pos, std::forward<Args>(args)...);
    }
    


  • Ich muss das nochmal aufgreifen...

    Wie kann ich es bewerkstelligen, dass diese Templatefunktion als 2. - n.Parameter nur lvalue-Referenzen akzeptiert?

    // Template function to extend get(index, A&) to get(index, A&, B&, C&, ...)
    template <class T, class... Args>
    typename std::enable_if<!std::is_pointer<T>::value, uint16_t>::type
    get(uint16_t index, T& v, Args&&... args) {
      uint16_t pos = get(index, v);
      return get(pos, std::forward<Args>(args)...);
    }
    

    Bisher verläuft sich der Compiler nämlich bei dem folgenden Call (für den es eine explizite Funktion gibt) und findet keine Auflösung:

    uint16_t a;
    get(20, a, 5);
    

    Er versucht, die 5 bei der Instanziierung als Referenz zu benutzen, was natürlich nicht geht.

    Es gibt ja std::is_reference, aber wie verknüpfe ich das mit dem !std::is_pointer?



  • @Miq sagte in Rekursive Templatefunktion:

    Es gibt ja std::is_reference, aber wie verknüpfe ich das mit dem !std::is_pointer?

    mit UND?



  • Ehm, ja, prinzipiell wohl std::is_reference && !std::is_pointer aber ich komme mit den ganzen < > durcheinander... 😟
    Und dar allererste Parameter darf ja ein rvalue sein.



  • Args& statt Args&& und das std::forward weglassen. Am enable_if brauchst du dazu nichts ändern.



  • Es wäre aber vermutlich schlauer den verschiedenen get Funktionen einfach andere Namen zu verpassen.



  • Also z.B. einfach so:

    template <typename T>
    void get_one_impl(uint16_t& index, T& retval) {
      uint16_t sz = sizeof(retval);    // Size of value to be read
    
      retval = 0;                      // return value
    
      // Will it fit?
      if (index <= MM_data.size() - sz) {
        // Yes. Copy it MSB first
        while (sz) {
          sz--;
          retval <<= 8;
          retval |= MM_data[index++];
        }
      }
    }
    
    template <typename... Ts>
    uint16_t get(uint16_t index, Ts&... retvals) {
        (get_one_impl(index, retvals), ...);
        return index;
    }
    


  • @hustbaer sagte in Rekursive Templatefunktion:

    Args& statt Args&& und das std::forward weglassen. Am enable_if brauchst du dazu nichts ändern.

    Nö, ich glaube nicht, denn genau das war ja die Lösung für das ältere Problem - siehe oben in diesem Thread.

    Und mit unterschiedlich benannten Funktionen geht das natürlich, ich habe aber einfach aus Erkenntnisgründen vor, das alles per overloading zu machen. Wenn's nicht geht, gut, aber so schnell möchte ich das nicht aufgeben 😉

    [Update] Aber Du hast mich auf eine andere Idee gebracht. Ich muss mal versuchen, die innere Funktion mit anderem Namen protected zu machen und den Einfachfall uint16_t get(uint16_t index, T&) mit der Templatefunktion mit abzudecken.



  • @Miq sagte in Rekursive Templatefunktion:

    Nö, ich glaube nicht, denn genau das war ja die Lösung für das ältere Problem - siehe oben in diesem Thread.

    Nö. In deinem ursprünglichen Code hattest du Args und nicht Args&.

    Und mit unterschiedlich benannten Funktionen geht das natürlich, ich habe aber einfach aus Erkenntnisgründen vor, das alles per overloading zu machen. Wenn's nicht geht, gut, aber so schnell möchte ich das nicht aufgeben

    Natürlich geht es. Aber wozu?



  • @hustbaer sagte in Rekursive Templatefunktion:

    Nö. In deinem ursprünglichen Code hattest du Args und nicht Args&.

    Stimmt, hast Recht! 👍



  • So geht es:

    // getOne() - read a MSB-first value starting at byte index. Returns updated index
    template <typename T> 
    uint16_t getOne(uint16_t index, T& retval) {
      uint16_t sz = sizeof(retval);    // Size of value to be read
    
      retval = 0;                      // return value
    
      // Will it fit?
      if (index <= MM_data.size() - sz) {
        // Yes. Copy it MSB first
        while (sz) {
          sz--;
          retval <<= 8;
          retval |= MM_data[index++];
        }
      }
      return index;
    }
    
    // Recursion termination helper
    uint16_t get(uint16_t index) { return index; }
    
    // Template function to extend get(index, A&) to get(index, A&, B&, C&, ...)
    template <class T, class... Args>
    typename std::enable_if<!std::is_pointer<T>::value, uint16_t>::type
    get(uint16_t index, T& v, Args&... args) {
      uint16_t pos = getOne(index, v);
      return get(pos, args...);
    }
    

    Ich brauche aber den Einzeiler zum Beenden der Rekursion, weil leider ein leeres Pack immer noch ein Pack ist.

    Danke schön @hustbaer , Deine Anregung hat mich weiter gebracht! 👍



  • Oder du verwendest eben einfach ne Fold-Expression statt der Rekursion. Siehe https://www.c-plusplus.net/forum/topic/352441/rekursive-templatefunktion/10

    if constexpr ginge auch:

    template <class T, class... Args>
    typename std::enable_if<!std::is_pointer<T>::value, uint16_t>::type
    get(uint16_t index, T& v, Args&... args) {
      uint16_t pos = getOne(index, v);
      if constexpr (sizeof...(Args) > 0)
        return get(pos, args...);
      else
        return pos;
    }
    

    Aber wozu kompliziert wenn's mit Fold-Expression noch viel einfacher geht? Macht auch weniger Template-Bloat.


Log in to reply