[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() { }
AltLog
macht 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
AltLog
diewrite()
-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
printf
undwrite
differenziert. 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
printf
undwrite
differenziert. 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
Serial
erklären -Serial
ist eine Instanz vonHardwareSerial
, die wiederum vonPrint
abgeleitet ist.Print
ist eine abstrakte Basisklasse, die zwingend die Implementierung vonwrite()
erfordert (write
ist inPrint
virtuell), definiert aber unzählige weitere print-Varianten, die inPrint
mit Hilfe vonwrite
implementiert sind.Die Templatefunktion der abgeleiteten Klasse
AltLog
sollte eigentlich - so mein Verständnis - die Funktion gleicher Signatur in der Basisklasse überladen.Die Anwendung des Funktionstemplate mit der von
Print
vorgegebenen Signatur fürprintf
ist 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 BasisklassePrint
aufgerufen wird und nicht die der abgeleiteten KlasseAltLog
. Dawrite
inPrint
virtuell 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
device
zur 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 vonPrint
abgeleitete 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 alsvirtual
deklariert, 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.