Variadic template funktioniert nicht wie gedacht
-
Hi,
ich versuche gerade, mir ein variadisches (schreibt man das so im Deutschen?) Funktionstemplate zu bauen, das als Argumente übergebene Werte in einer bestimmten Form in ein Zielarray schreibt. Das sieht bisher so aus:
// add() variant to copy a buffer into MM_data. Returns updated MM_index or 0 uint16_t add(uint16_t count, uint8_t *arrayOfBytes); // Terminate args recursion uint16_t add() { return 0; } // add() - add a single data element MSB first to MM_data. Returns updated MM_index or 0 template <class T> uint16_t add(T v) { uint16_t sz = sizeof(T); // Size of value to be added // Will it fit? if (MM_data && sz <= (MM_len - MM_index)) { // Yes. Copy it MSB first while (sz) { sz--; MM_data[MM_index++] = (v >> (sz << 3)) & 0xFF; } // Return updated MM_index (logical length of message so far) return MM_index; } // No, will not fit - return 0 return 0; } // First arg is uint8_t template <uint8_t, class... Args> uint16_t add(uint8_t v, Args... args) { return add(v) + add(args...); } // First arg is uint16_t template <uint16_t, class... Args> uint16_t add(uint16_t v, Args... args) { return add(v) + add(args...); } // First arg is uint32_t template <uint32_t, class... Args> uint16_t add(uint32_t v, Args... args) { return add(v) + add(args...); }
- Die erste Variante ist der Sonderfall, bei der ein anderes Array angefügt wird (deswegen Länge/Pointer als Argumente).
- Die zweite soll die Rekursion über die Argumente stoppen, wenn alle Argumente verbraucht sind.
- Die dritte ist die eigentliche Wirkfunktion, die ein einzelnes Argument schreibt.
Bis hierher funktioniert es auch.
- Die drei letzten Varianten sollen eine Kette von Argumenten der Typen uint8_t, uint16_t und uint32_t abarbeiten. Wenn ich die nicht alle drei explizit angebe, gibt es Zweideutigkeiten mit der ersten Variante oben.
Ich habe im Programm jetzt z.B. ein
add(uint8_t, uint8_t, uint16_t, uint16_t);
- natürlich mit Variablen des jeweiligen Typs anstelle der Typnamen. Das compiliert ohne Probleme, nur mault der Linker dann (die Klasse, in deradd()
verwendet wird, heißtTCPRequest
. Sie erbtadd()
von einer Basisklasse, in deren Header der Code oben steht):no instance of overloaded function "TCPRequest::add" matches the argument list -- argument types are: (uint8_t, uint8_t, uint16_t, uint16_t) -- object type is: TCPRequest
Wo liegt mein Denkfehler?
-
@Miq sagte in Variadic template funktioniert nicht wie gedacht:
mault der Linker
Wie kommst du darauf?
-
Willst du 1 und viele nicht lieber in verschiedene Namen trennen und das variadic template auch 1 Argument behandeln lassen?
Also:
template <class... Args> uint16_t add(Args... args) { return (add_impl(args) + ... + 0); }
Dein add mit einem Argument in add_impl umbenennen. Die 0-Argument-Funktion kann dann ebenfalls wegfallen.
-
@manni66 sagte in Variadic template funktioniert nicht wie gedacht:
@Miq sagte in Variadic template funktioniert nicht wie gedacht:
mault der Linker
Wie kommst du darauf?
Compiler, klar. Sorry.
-
@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 mitadd<5, ...>()
oder sowas instanzieren. Ist das wirklich, was du hier vorhast? Ich denke du willst hier wohl eher nur eintemplate <class... Args>
haben. Dass der erste Parameter einuint8_t
ist, legst du ja hier schon über den ersten Funktionsparameter fest.
-
@wob sagte in Variadic template funktioniert nicht wie gedacht:
Willst du 1 und viele nicht lieber in verschiedene Namen trennen und das variadic template auch 1 Argument behandeln lassen?
Also:
template <class... Args> uint16_t add(Args... args) { return (add_impl(args) + ...); }
Dein add mit einem Argument in add_impl umbenennen.
Okay, kann ich so machen.
Das Problem besteht aber jetzt darin, dass die explizit überladene Version
uint16_t add(uint16_t count, uint8_t *arrayOfBytes);
nicht gefunden wird, sondern auch da ein Template angewendet werden soll. Das scheitert dann daran, dass dieadd()
mit einem Argument nicht mituint8_t *
umgehen kann.
-
@Miq sagte in Variadic template funktioniert nicht wie gedacht:
Das Problem besteht aber jetzt darin, dass die explizit überladene Version
uint16_t add(uint16_t count, uint8_t *arrayOfBytes);
nicht gefunden wird, sondern auch da ein Template angewendet werden soll. Das scheitert dann daran, dass dieadd()
mit einem Argument nicht mituint8_t *
umgehen kann.Aber warum? Daran wollte ich doch nix ändern.
Also vollständig:
#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; } template <class... Args> uint16_t add(Args... args) { return (add_impl(args) + ... + 0); } int main() { // return add((uint8_t)1, 3, 5); // return add(); return add(uint16_t(1234), (uint8_t*) nullptr); }
-
@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 mitadd<5, ...>()
oder sowas instanzieren. Ist das wirklich, was du hier vorhast? Ich denke du willst hier wohl eher nur eintemplate <class... Args>
haben. Dass der erste Parameter einuint8_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
undis_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 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.