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&
stattArgs&&
und dasstd::forward
weglassen. Amenable_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&
stattArgs&&
und dasstd::forward
weglassen. Amenable_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 nichtArgs&
.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 nichtArgs&
.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.