Variadic template funktioniert nicht wie gedacht
-
@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 denadd
-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 viastd::enable_if<(sizeof...(Args) > 0)>
probiert, was auch zu funktionieren scheint: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üruint8_t
,uint16_t
, etc. machst, wenn du die daraus gewonnene Information inadd(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 alsadd(3, arrayOfBytes)
aufgerufen hast. Das Literal3
hat den Typint
, die Funktionadd(uint16_t count, uint8_t*)
erwartet aber einenuint16_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 einenuint16_t
oder man schliesst die Pointer-Argumente beim templatisiertenadd
aus (enable_if
), so dass nur diearrayOfBytes
-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 mittemplate <class T, class... Args> add(T v, Args... args)
wäre. Vorher, mittemplate <class... Args> uint16_t add(uint8_t v, Args... args)
war das nicht der Fall, da dieses spezialisierter alstemplate <class T> uint16_t add(T v)
ist (exakter Typ fürv
, ohne Konvertierung).Kurzum, so funkionierts auch - Ansatz von @wob bei dem ich mit
enable_if
Pointer-Argumente ausschliesse, damit in dem Fall derarrayOfBytes
-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...); }
Damit das
enable_if
einfacher zu formulieren ist, habe ich auch die Parameter-Reihenfolge desarrayOfBytes
-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