[Gelöst] Compiler verwendet Templatefunktion nicht?
-
Ich suche nach einer Möglichkeit, einen seriellen Output zur Laufzeit auf mehrere Kanäle zu duplizieren. Entwicklungsumgebung ist PlatformIO, C++11 und ein Espressif ESP32 mit Arduino-Core als Zielsystem.
Minimal funktionaler Code zur Erläuterung:
#include <Arduino.h> class AltLog : public Print { public: size_t write(uint8_t c) { Serial.printf("/1: %c", c); Serial.printf("/2: %c", c); return 5; } template <typename... Args> size_t printf(const char *format, Args&&... args) { size_t len = 0; char fmt[strlen(format) + 4]; strcpy(fmt, "/1: "); strcat(fmt, format); len = Serial.printf(fmt, std::forward<Args>(args) ...); strcpy(fmt, "/2: "); strcat(fmt, format); len = Serial.printf(fmt, std::forward<Args>(args) ...); return len; } }; AltLog a; Print *device = &Serial; void setup() { Serial.begin(115200); Serial.println(""); Serial.println("_OK_"); delay(5000); device->printf("Serial.\n"); device = &a; device->printf("Alternate.\n"); } void loop() { }AltLogmacht nichts anderes, als die Ausgabe zweimal auf Serial auszugeben - ist eben nur als Erklärung gedacht.Die Ausgabe des obigen Programms ist
_OK_ Serial. /1: A/2: A/1: l/2: l/1: t/2: t/1: e/2: e/1: r/2: r/1: n/2: n/1: a/2: a/1: t/2: t/1: e/2: e/1: ./2: ./1: /2:Es funktioniert zwar, aber man sieht, dass bei der Verwendung von
AltLogdiewrite()-Funktion benutzt wird, obwohl über die Templatefunktion sehr wohlprintf()verfügbar wäre.Warum passiert das? Und kann ich das ändern?
Hintergrund:
write()ist sehr ineffizient, wenn die Ausgabekanäle z.B. TCP-Verbindungen sind, weil da für jedes Zeichen an jeden Client ein Paket geschickt würde.
-
Der obige Code ist kein minimales, reproduzierendes Beispiel deines Problems. Ich kann nicht nachvollziehen, was genau zwischen
printfundwritedifferenziert. Oder wie das geschehen soll. (Arduino ist eine C-Bibliothek, wie soll diese indirekt ein Funktionstemplate aufrufen?)Vielleicht solltest Du eher direkt im Arduino Forum fragen, oder mal in den Header eintauchen und die relevante Maschinerie posten?
-
@Miq sagte in Compiler verwendet Templatefunktion nicht?:
Print *device = &Serial;
Du zeigst und nicht, wie Print definiert ist.
Es funktioniert zwar, aber man sieht, dass bei der Verwendung von AltLog die write()-Funktion benutzt wird, obwohl über die Templatefunktion sehr wohl printf() verfügbar wäre.
Das sieht man dort nicht. Man sieht nur, dass mit
device->printf("Alternate.\n");doch offenbar printf aufgerufen wird - das von Print.Versuchst du, virtuelle getemplatete Funktionen zu haben?
Ah, Print scheint die Klasse hier zu sein: https://github.com/esp8266/Arduino/blob/master/cores/esp8266/Print.h mit
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));Du hast ja einen Pointer auf Print, nicht auf AltLog. Das ->printf ist immer das aus der Print-Klasse. (falls das deine Frage war)
-
@Columbo sagte in Compiler verwendet Templatefunktion nicht?:
Der obige Code ist kein minimales, reproduzierendes Beispiel deines Problems. Ich kann nicht nachvollziehen, was genau zwischen
printfundwritedifferenziert. Oder wie das geschehen soll. (Arduino ist eine C-Bibliothek, wie soll diese indirekt ein Funktionstemplate aufrufen?)Die Erwähnung von Arduino sollte nur die Verwendung von
Serialerklären -Serialist eine Instanz vonHardwareSerial, die wiederum vonPrintabgeleitet ist.Printist eine abstrakte Basisklasse, die zwingend die Implementierung vonwrite()erfordert (writeist inPrintvirtuell), definiert aber unzählige weitere print-Varianten, die inPrintmit Hilfe vonwriteimplementiert sind.Die Templatefunktion der abgeleiteten Klasse
AltLogsollte eigentlich - so mein Verständnis - die Funktion gleicher Signatur in der Basisklasse überladen.Die Anwendung des Funktionstemplate mit der von
Printvorgegebenen Signatur fürprintfist IMHO eine reine Compilersache und hat nichts mit der semantischen Bedeutung der Klassen zu tun. Ich denke, ich könnte das Ganze auch völlig abstrakt mit eigenen Klassen neu bauen - dann wird der Code aber entsprechend länger, fürchte ich.Meine - unbestätigte - Vermutung bisher ist, dass durch den als
Print *device;definierten Pointer die Funktion der BasisklassePrintaufgerufen wird und nicht die der abgeleiteten KlasseAltLog. DawriteinPrintvirtuell ist, wird im Endeffekt dannAltLog.write()aufgerufen.Vielleicht solltest Du eher direkt im Arduino Forum fragen, oder mal in den Header eintauchen und die relevante Maschinerie posten?
Das mit den Arduinoforen habe ich bereits versucht - da war der Tenor so ungefähr: "Wenn es nicht geht, dann mach' es nicht" - ich möchte aber verstehen, warum der Compiler hier so entscheidet.
-
@wob sagte in Compiler verwendet Templatefunktion nicht?:
@Miq sagte in Compiler verwendet Templatefunktion nicht?:
Print *device = &Serial;
Du zeigst und nicht, wie Print definiert ist.
Es funktioniert zwar, aber man sieht, dass bei der Verwendung von AltLog die write()-Funktion benutzt wird, obwohl über die Templatefunktion sehr wohl printf() verfügbar wäre.
Das sieht man dort nicht. Man sieht nur, dass mit
device->printf("Alternate.\n");doch offenbar printf aufgerufen wird - das von Print.Versuchst du, virtuelle getemplatete Funktionen zu haben?
Ah, Print scheint die Klasse hier zu sein: https://github.com/esp8266/Arduino/blob/master/cores/esp8266/Print.h mit
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));Du hast ja einen Pointer auf Print, nicht auf AltLog. Das ->printf ist immer das aus der Print-Klasse. (falls das deine Frage war)
Das hat sich gekreuzt... Ja, genau das ist meine Vermutung. Wenn tatsächlich
Print.printf()stattAltLog.printf()aufgerufen wird: wie kann ich (sauber) den Aufruf der überladenen Funktion erzwingen?AltLog *device;geht ja nicht, weil danndevice = &Serial;nicht mehr akzeptiert wird.
-
Was natürlich geht, ist
dynamic_cast<AltLog *>(device)->printf("Alternate.%c\n", 'A');- aber dann müsste ich zur Compilezeit wissen, auf welchen Typ
devicezur Laufzeit zeigt.
Das ließe sich eventuell mit einer hässlichen Präprozessorkrücke einbauen, aber

- aber dann müsste ich zur Compilezeit wissen, auf welchen Typ
-
Innerhalb von AltLog::printf wird serial.printf aufgerufen, genau wie bei AltLog::write.
-
@Tyrdal sagte in Compiler verwendet Templatefunktion nicht?:
Innerhalb von AltLog::printf wird serial.printf aufgerufen, genau wie bei AltLog::write.
"AltLog macht nichts anderes, als die Ausgabe zweimal auf Serial auszugeben - ist eben nur als Erklärung gedacht."
-
Für meinen speziellen Fall habe ich eine Lösung gefunden.
Im Arduino/ESP32-Core ist in der
Print-Klasse auchprintf()implementiert. Dieses verwendet intern eine Funktionsize_t write(const uint8_t *buffer, size_t size)für die Ausgabe der formatierten Zeichenkette.Die interne Implementierung dieser
write()-Variante ruft dann für jedes Zeichen die "pure virtual" Funktionsize_t write(uint8_t c)auf, die jede vonPrintabgeleitete Klasse implementieren muss. Daher wird der Aufruf vondevice->printf()wie folgt ausgeführt:Print::printf() --> Print::write(buffer, size) --> AltLog::write(c)Damit ist klar, dass das
AltLog::printf()sowieso ignoriert wird.Was mir entgangen war: die Funktion
size_t Print::write(const uint8_t *buffer, size_t size)ist ebenfalls alsvirtualdeklariert, so dass man sie problemlos überladen kann.Ich habe also
AltLog::printf()durchAltLog::write()ersetzt, wodurch die Aufrufsequenz jetztPrint::printf() --> AltLog::write(buffer, size)lautet und die Zeichenkette in einem Rutsch ausgegeben werden kann.