Funktion für mehrere Klassen

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Funktion für mehrere Klassen

Beitrag von Krishty »

Jonathan hat geschrieben: 16.10.2020, 10:57Es wurde angesprochen, dass dynamic_cast zuweilen sehr ineffizient sein kann. Wie ist dann die best practise? Ist es wirklich besser z.B. in der Basisklasse ein enum einzubauen, anhand dessen man entscheiden kann um welche abgeleitete Klasse es sich handelt und basierend darauf einen static_cast zu machen?
„Best“ ist das sicher nicht, aber zumindest „Common“ Practice. Ich hab’s z.B. bei S.T.A.L.K.E.R. so gesehen (deren Klassenhierarchien sind auch ein Alptraum).

Heutzutage nutzen Spiele AFAIK Entity-Component-Frameworks, um genau solche Probleme zu umgehen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Funktion für mehrere Klassen

Beitrag von Schrompf »

Ja, das passt schon ganz gut. Gibt aber überall noch Einschränkungen:
Jonathan hat geschrieben: 16.10.2020, 10:57 - reinterpret_cast: Deaktiviert einfach die Typprüfung und nimmt die Speicheradresse wie sie ist. Kann bei komplexen Klassenhierarchien ungültige Zeiger erzeugen, dann knallt es. Für diesen Anwendungsfall nie eine gute Idee.
reinterpret_cast ist außerdem auf Typen der gleichen Speichergröße beschränkt. Du kannst nen Pointer in einen anderen Pointer casten. Du kannst ne struct { float a, b, c; } in eine struct { int x, y, z; } casten. Aber Du kannst auch mit nem reinterpret_cast nicht einen MemberFunctionPtr in einen FunctionPtr konvertieren. Und auch keinen double in einen float.

Wobei ich bei letzterem nicht ganz sicher bin, ob der dann nicht einfach aus Bequemlichkeit nen C-Cast annimmt und den Wert konvertiert.
- static_cast: Prüft ob die Klassen kompatibel sind (man kann also keine zwei vollkommen unabhängigen Klassen casten?) und biegt gegebenenfalls die Zeiger korrekt um. Funktioniert, wenn man sich sicher ist, dass man ein Objekt der abgeleiteten Klasse hat.
static_cast geht nur für a) verwandte Typen, also Ableitungen oder b) Zeiger von und zu void* oder c) Konvertierungen, also uint32_t zu uint16_t.
- dynamic_cast: Führt eine Laufzeitprüfung durch und ist damit die sicherste Variante. Allerdings auch die langsamste.
Und der dynamic_cast geht nur zwischen Ableitungen einer Basisklasse mit mindestens einer virtuellen Funktion. Der ist wirklich nur zur Arbeit in Ableitungshierarchien, der bringt Dir gar nix, wenn Dir jemand einen void* reicht und Du rauskriegen willst, ob da floats oder int32_t liegen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Funktion für mehrere Klassen

Beitrag von Spiele Programmierer »

Jonathan hat geschrieben: 16.10.2020, 10:57Es wurde angesprochen, dass dynamic_cast zuweilen sehr ineffizient sein kann. Wie ist dann die best practise? Ist es wirklich besser z.B. in der Basisklasse ein enum einzubauen, anhand dessen man entscheiden kann um welche abgeleitete Klasse es sich handelt und basierend darauf einen static_cast zu machen?
Wenn das geht am besten die Objekte gar nicht mischen, das ist besonders was Performance anbelangt unübertroffen. Selbst wenn du die Objekte nur via virtuellen Funktionen zugreifst (ohne dynamic_cast), sind gemischte Objekte sehr ungünstig da die Sprungvorhersage jedesmal falsch liegen wird. Das so zu organisieren, dass die Objekte nicht gemischt werden, läuft wohl unter dem Schlagwort Entity-Component-Systeme. Wobei ich den Begriff nicht sonderlich mag, da sich das so kompliziert und starr anhört.

Manchmal ist es aber auch einfacher, die Objekte wenigstens gemeinsam abzuspeichern. Die Möglichkeit mit der Enumeration besteht sicherlich und das hab ich auch schon öfter gesehen. Wenn man es erweiterbar halten will, kann man auch einen Zeiger nehmen. Also z.B. so:

Code: Alles auswählen

struct BaseIdentifier {};
struct Base
{
    BaseIdentifier const* Identifier;
};

struct Derived : public Base
{
    static const BaseIdentifier Identifier; // Wobei man hier in der Quelldatei auch noch die Variable definieren muss. Achtung bei "constexpr" außerhalb von Klassen, das kann mehrfach instanziert werden sodass der Zeiger zu einem solchen "Identifier" nicht mehr eindeutig ist.
    Derived()
      : Base { &Identifier }
    {}
};
Dieses Vorgehen hat den Vorteil (wenn man dies braucht!), dass man auch in unabhängigen Code noch Klassen hinzufügen kann. Z.B. sogar in Plugin-DLLs. Außerdem geht es mir oft so, dass ich gerne noch ein paar weitere Meta-Eigenschaften zu den Klassen abspeichern möchte. Zum Beispiel den Namen, einen Zeiger zu einer Funktion die eine frische Instanz der Klasse erstellt, und ggf. weitere solche Daten die man in anderen Sprachen teilweise auch über Reflection bekäme. Solche Dinge kann man prima mit in die BaseIdentifier-Klasse im obigen Beispiel packen und damit beispielsweise eine Liste aller verschiedenen Klassen erstellen.

Ich habe bei mir oft RTTI deaktiviert, was man für typeid und dynamic_cast braucht. Teilweise ist das aber ehrlich gesagt inzwischen auch eher aus Gewohnheit. In Code der nicht performancekritisch ist, spricht meiner Meinung nach nicht grundsätzlich etwas gegen diese Sprachmittel. Selbst wenn man eine Liste unbekannter Objekte hat, sollte man ja nach Möglichkeit auch eh nicht so irre viel rumkonvertieren sondern wenn dann eher virtuelle Funktionen aufrufen.
Schrompf hat geschrieben: 16.10.2020, 12:41 reinterpret_cast ist außerdem auf Typen der gleichen Speichergröße beschränkt. Du kannst nen Pointer in einen anderen Pointer casten. Du kannst ne struct { float a, b, c; } in eine struct { int x, y, z; } casten. Aber Du kannst auch mit nem reinterpret_cast nicht einen MemberFunctionPtr in einen FunctionPtr konvertieren. Und auch keinen double in einen float.
Hm, ich weiß nicht wie du das meinst. Also das geht ja z.B. nicht:

Code: Alles auswählen

struct s { float a, b, c; };
struct t { int x, y, z; };
t cast(s obj) { return reinterpret_cast<t>(obj); }
Soweit ich weiß geht reinterpret_cast nur für alle Zeiger- und Referenztypen. Und dann ist die Größe egal.

Ein wichtiger Haken ist noch, dass man dann nicht auf die umgebogenen Zeiger zugreifen darf (außer es sind char-Zeiger-Typen zu denen du konvertierst), zumindest wenn man dem C++-Standard treu sein will. Das ist diese Strict-Aliasing-Regel.

reinterpret_cast ist zum Beispiel dann die richtige Wahl, wenn du irgendwo eine Struktur hast (z.B. in einer Bibliothek) und du deinen Zeiger darin speichern willst. Dann kannst du den Zeiger erst nach void* konvertieren, in der Struktur abspeichern und dann zu einem späteren Zeitpunkt mit reinterpret_cast wieder zu einem Zeiger zu deinem Typ zurück konvertieren und verwenden. Das ist erlaubt. Was nicht erlaubt ist, ist z.B. einen float-Zeiger in einen int32_t-Zeiger umzuwandeln und dann zuzugreifen. Das ist nur mit memcpy oder, teilweise, mit union erlaubt.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Funktion für mehrere Klassen

Beitrag von Krishty »

Spiele Programmierer hat geschrieben: 16.10.2020, 14:13reinterpret_cast ist zum Beispiel dann die richtige Wahl, wenn du irgendwo eine Struktur hast (z.B. in einer Bibliothek) und du deinen Zeiger darin speichern willst. Dann kannst du den Zeiger erst nach void* konvertieren, in der Struktur abspeichern und dann zu einem späteren Zeitpunkt mit reinterpret_cast wieder zu einem Zeiger zu deinem Typ zurück konvertieren und verwenden. Das ist erlaubt.
Das Ironische ist, dass man dafür static_cast einsetzen sollte ;) (Code-Analyse-Tools à Clang-Tidy spucken auch entsprechende Warnungen aus.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Funktion für mehrere Klassen

Beitrag von Spiele Programmierer »

Wow, ich hätte nicht gedacht, das der Compiler einen static_cast von void* schluckt. Es ergibt wohl in so fern Sinn, als das static_cast ja auch andere Downcasts kann.

Auch wieder was gelernt. Da kann ich wohl ein paar reinterpret_casts bei mir entfernen. :P

Offen gesagt, hab dann ich auch kaum noch gute Verwendungszwecke für reinterpret_cast in gültigen C++. Höchstens richtig schlecht designte Interfaces wie einige Intel-Intrinsics wie z.B. _mm_storeu_si128, das ein __m128i* will.

EDIT:
Und natürlich wenn man via char-Typen direkt zugreifen will. Aber auch wenn man nicht Byte-Weise unterwegs sein will, kann man auf GCC/Clang __attribute__((__may_alias__)) verwenden und Microsoft unterstützt diesen Fall sowieso. Das ist wohl auch noch ein Anwendungsfall.

EDIT2:
Habe gerade mal bisschen durch meinen Code geguckt und noch drei Fälle identifiziert, wofür man reinterpret_cast verwenden muss:
  1. Um mit den Argumenten zu memcpy und Konsorten Zeiger-Arithmetik zu machen. Mit static_cast kann man zwar auch zu char* konvertieren, aber ich vermute, dass dies streng genommen nicht erlaubt ist, wenn es sich nicht tatsächlich um einen Zeiger zu einem "char-Objekt" handelt. Habe jetzt im Standard nachgeschaut (siehe Post weiter unten). reinterpret_cast ist bei Objektzeiger-Casts von und zu void*-Casts äquivalent zu static_cast. Hier kann man wohl auch static_cast nehmen, wenn man will.
  2. Als Fallback für Template-Code, wenn if constexpr nicht unterstützt wird und mit if nachgestellt wird. Dann kann man mit reinterpret_cast dafür sorgen, dass der Compiler Ruhe gibt. Wenn der Zweig doch ausgeführt wird, ist die Konvertierung dann vom Typ zu sich selbst und damit kein Problem.
  3. Um mehrere verschiedene Funktionszeigertypen in einer Variable abzuspeichern. Man kann mit static_cast (natürlich) immernoch nicht void (*)(int) in void (*)() umwandeln. Mit reinterpret_cast schon.
Zuletzt geändert von Spiele Programmierer am 16.10.2020, 23:26, insgesamt 1-mal geändert.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Funktion für mehrere Klassen

Beitrag von Krishty »

Spiele Programmierer hat geschrieben: 16.10.2020, 14:32Und natürlich wenn man via char-Typen direkt zugreifen will. Aber auch wenn man nicht Byte-Weise unterwegs sein will, kann man auf GCC/Clang __attribute__((__may_alias__)) verwenden und Microsoft unterstützt diesen Fall sowieso. Das ist wohl auch noch ein Anwendungsfall.
Dateiformate und Protokolle sind auch bei mir noch ein riesen Batzen reinterpret_casts, der wird aber weiter und weiter durch memcpy() abgelöst. Auf Aliasing-Verletzungen würde ich nicht bauen – die nicht-standard-Konformität ist das eine; der eklige Maschinencode das Sahnetüpfel.
[*]Um mit den Argumenten zu memcpy und Konsorten Zeiger-Arithmetik zu machen. Mit static_cast kann man zwar auch zu char* konvertieren, aber ich vermute, dass dies streng genommen nicht erlaubt ist, wenn es sich nicht tatsächlich um einen Zeiger zu einem "char-Objekt" handelt.
Fun fact: In C++ aliast alles mit char und dessen Varianten. Das ist tatsächlich zuweilen auch ein Problem bei der Optimierung.
[*]Um mehrere verschiedene Funktionszeigertypen in einer Variable abzuspeichern. Man kann mit static_cast (natürlich) immernoch nicht void (*)(int) in void (*)() umwandeln. Mit reinterpret_cast schon.
Verstehe ich nicht. Warum sollte man das tun :(
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Funktion für mehrere Klassen

Beitrag von Spiele Programmierer »

Krishty hat geschrieben: 16.10.2020, 19:36Dateiformate und Protokolle sind auch bei mir noch ein riesen Batzen reinterpret_casts, der wird aber weiter und weiter durch memcpy() abgelöst. Auf Aliasing-Verletzungen würde ich nicht bauen – die nicht-standard-Konformität ist das eine; der eklige Maschinencode das Sahnetüpfel.
Hast du ein Beispiel für schlechten Maschinencode in dem Anwendungfall?

Die Erfahrung bezüglich Codegenerierung an die ich mich da erinnere, waren nämlich eher andersrum. Wobei die Compiler-Unterstützung mit memcpy, glaub ich, wirklich besser geworden ist. Jedenfalls erzeugen alle aktuellen Compiler bei einem kurzen Test von mir auf Godbolt vernünftigen Code für x86/ARM für die paar gängigen Fälle die ich auf die Schnelle ausprobiert habe
Krishty hat geschrieben: 16.10.2020, 19:36
Um mit den Argumenten zu memcpy und Konsorten Zeiger-Arithmetik zu machen. Mit static_cast kann man zwar auch zu char* konvertieren, aber ich vermute, dass dies streng genommen nicht erlaubt ist, wenn es sich nicht tatsächlich um einen Zeiger zu einem "char-Objekt" handelt.
Fun fact: In C++ aliast alles mit char und dessen Varianten. Das ist tatsächlich zuweilen auch ein Problem bei der Optimierung.
Das ist hier aber egal. Innerhalb memcpy und memset aliast jedenfalls sowieso nichts und es ist sicherlich besser als memcpy oder memset für große Datenmengen selbst zu schreiben.
Krishty hat geschrieben: 16.10.2020, 19:36
Um mehrere verschiedene Funktionszeigertypen in einer Variable abzuspeichern. Man kann mit static_cast (natürlich) immernoch nicht void (*)(int) in void (*)() umwandeln. Mit reinterpret_cast schon.
Verstehe ich nicht. Warum sollte man das tun :(
Man sollte das tun, wenn man mit beliebigen Funktionszeigern zu tun hat und nicht alles templatisieren will oder kann. Relevant z.B. wenn man dynamische Codegenierierung betreibt, wenn man mit DLLs/SOs via Windows GetProcAddress/Linuxes dlsym rumhantiert oder wenn man erweiterbare APIs verwendet wie z.B. OpenGL (wglGetProcAddress/eglGetProcAddress/glXGetProcAddress/...) oder Vulkan (vkGetInstanceProcAddr).
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Funktion für mehrere Klassen

Beitrag von Krishty »

Spiele Programmierer hat geschrieben: 16.10.2020, 21:11
Krishty hat geschrieben: 16.10.2020, 19:36
Um mit den Argumenten zu memcpy und Konsorten Zeiger-Arithmetik zu machen. Mit static_cast kann man zwar auch zu char* konvertieren, aber ich vermute, dass dies streng genommen nicht erlaubt ist, wenn es sich nicht tatsächlich um einen Zeiger zu einem "char-Objekt" handelt.
Fun fact: In C++ aliast alles mit char und dessen Varianten. Das ist tatsächlich zuweilen auch ein Problem bei der Optimierung.
Das ist hier aber egal. Innerhalb memcpy und memset aliast jedenfalls sowieso nichts und es ist sicherlich besser als memcpy oder memset für große Datenmengen selbst zu schreiben.
Hehe, nein – du missverstehst mich. Was ich sage, ist: *jeder* char-Zeiger verhagelt dir *überall in seiner Nähe* die Code Generation weil C++ vorschreibt, dass char * mit jedem Objekt aliasen darf.

Guckst du: https://godbolt.org/z/Gnq4ox

In intAndCharAlias() muss der Compiler jeden Zugriff aufs int durch den Speicher zwingen, weil eine Änderung am char das int ändern könnte (und umgekehrt), denn char aliast *alles*.

In intAndShortDont() kann er nach Belieben optimieren.

Jeder char * hat krasse Auswirkungen auf Code Generation, weil der Compiler unter Zwang gerät, zu beweisen, dass kein Aliasing stattfindet.

Du hast angenommen, dass eine Umwandlung beliebiger Typen zu char * „streng genommen nicht erlaubt” wäre. Doch, das ist ein (sehr Performance-feindliches) standardkonformes Feature :)
Spiele Programmierer hat geschrieben: 16.10.2020, 21:11
Krishty hat geschrieben: 16.10.2020, 19:36
Um mehrere verschiedene Funktionszeigertypen in einer Variable abzuspeichern. Man kann mit static_cast (natürlich) immernoch nicht void (*)(int) in void (*)() umwandeln. Mit reinterpret_cast schon.
Verstehe ich nicht. Warum sollte man das tun :(
Man sollte das tun, wenn man mit beliebigen Funktionszeigern zu tun hat und nicht alles templatisieren will oder kann. Relevant z.B. wenn man dynamische Codegenierierung betreibt, wenn man mit DLLs/SOs via Windows GetProcAddress/Linuxes dlsym rumhantiert oder wenn man erweiterbare APIs verwendet wie z.B. OpenGL (wglGetProcAddress/eglGetProcAddress/glXGetProcAddress/...) oder Vulkan (vkGetInstanceProcAddr).
Wenn man nicht templaten kann, okay. Aber
  1. ist jeder Cast nach LoadLibrary() oder dlsym() eine Verletzung des Sprachstandards
  2. würde ich dafür memcpy() von einem Zeiger in den anderen statt reinterpret_cast nutzen (ist zumindest keine so krasse Verletzung), und
  3. teste ich gerade aus, dass APIs ein struct aus Funktionszeigern zur Verfügung stellen (macht Vulkan das nicht?) – das ist 100 % sprachkonform und hat noch andere Vorteile, wie starke Versionierung. Falls es gut läuft, schreibe ich mal was drüber.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Funktion für mehrere Klassen

Beitrag von Spiele Programmierer »

Krishty hat geschrieben: 16.10.2020, 21:29Hehe, nein – du missverstehst mich. Was ich sage, ist: *jeder* char-Zeiger verhagelt dir *überall in seiner Nähe* die Code Generation weil C++ vorschreibt, dass char * mit jedem Objekt aliasen darf.

Guckst du: https://godbolt.org/z/Gnq4ox

In intAndCharAlias() muss der Compiler jeden Zugriff aufs int durch den Speicher zwingen, weil eine Änderung am char das int ändern könnte (und umgekehrt), denn char aliast *alles*.

In intAndShortDont() kann er nach Belieben optimieren.

Jeder char * hat krasse Auswirkungen auf Code Generation, weil der Compiler unter Zwang gerät, zu beweisen, dass kein Aliasing stattfindet.
Hm, danke für ausführlich Darstellung. Das ist mir nicht neu und das streite ich gar nicht ab. Vlt. habe ich mich zuvor unklar ausgedrückt. Zum einen ist es in meinen Fall aus dem Post gemeint habe, ist die Alternative void* was ja genauso wenig Aliasing-Garantien macht. Zum anderen habe ich speziell an folgende Situation gedacht:

Code: Alles auswählen

...
memset(reinterpret_cast<char*>(GenericPointer) + sizeOfDataElement * index, 0, sizeOfDataElement * countToCopy);
...
In dem Fall existiert, selbst wenn GenericPointer zu einer Basisklasse zeigt die nicht gealiast werden kann, der char-Zeiger nur vorübergehend um Arithmetik machen zu können und sollte eig. keinen Verlust von Aliasing-Informationen im Code außenrum verursachen.
Krishty hat geschrieben: 16.10.2020, 21:29Wenn man nicht templaten kann, okay. Aber
  1. ist jeder Cast nach LoadLibrary() oder dlsym() eine Verletzung des Sprachstandards
  2. würde ich dafür memcpy() von einem Zeiger in den anderen statt reinterpret_cast nutzen (ist zumindest keine so krasse Verletzung), und
  3. teste ich gerade aus, dass APIs ein struct aus Funktionszeigern zur Verfügung stellen (macht Vulkan das nicht?) – das ist 100 % sprachkonform und hat noch andere Vorteile, wie starke Versionierung. Falls es gut läuft, schreibe ich mal was drüber.
Punkt 2 überzeugt mich nicht und erinnert mich an Jonathans Argument pro reinterpret_cast versus static_cast. Bei einem reinterpret_cast<void (*)(int)>(...) weiß der Compiler wenigstens was ich mache und kann auf komischen Architekturen ggf. warnen.
Ich hab jetzt einfach mal den C++-Standard aufgemacht (zumindest dieses C++20-Working-Draft). Bei §7.6.1.9 Punkt 6 (Seite 114) steht:
C++20-Working-Draft §7.6.1.9 Punkt 6 hat geschrieben:A function pointer can be explicitly converted to a function pointer of a different type. [Note: The effect of calling a function through a pointer to a function type (9.3.3.5) that is not the same as the type used in the definition of the function is undefined (7.6.1.2). —end note] Except that converting a prvalue of type “pointer to T1 ” to the type “pointer to T2 ” (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified. [Note: See also 7.3.11 for more details of pointer conversions. —end note]
Wenn ich das richtig verstehe, sollte hier mit etwas guten Willen "back to its original type yields the original pointer value" greifen und es ist damit nicht mal "Implementation Defined" oder sowas sondern sogar standardkonform. Letzteres überrascht mich ehrlich gesagt sogar selbst.

Bezüglich Punkt 3: Jede Implementierung kann eigene Erweiterungen definieren. Natürlich gibt es Bibliotheken oder Codegeneratoren (z.B. GLEW und GLAD für OpenGL) die für offiziell veröffentlichte Erweiterungen Funktionssignaturen erstellen und entsprechende Casts generieren können. Trotzdem existieren diese Casts natürlich. Genauso wie wenn du einen eigenen Vulkan-Loader schreibst oder ein Vulkan-Backend.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Funktion für mehrere Klassen

Beitrag von Krishty »

Spiele Programmierer hat geschrieben: 16.10.2020, 23:04Punkt 2 überzeugt mich nicht und erinnert mich an Jonathans Argument pro reinterpret_cast versus static_cast. Bei einem reinterpret_cast<void (*)(int)>(...) weiß der Compiler wenigstens was ich mache und kann auf komischen Architekturen ggf. warnen.
Ich hab jetzt einfach mal den C++-Standard aufgemacht (zumindest dieses C++20-Working-Draft). Bei §7.6.1.9 Punkt 6 (Seite 114) steht:
C++20-Working-Draft §7.6.1.9 Punkt 6 hat geschrieben:A function pointer can be explicitly converted to a function pointer of a different type. [Note: The effect of calling a function through a pointer to a function type (9.3.3.5) that is not the same as the type used in the definition of the function is undefined (7.6.1.2). —end note] Except that converting a prvalue of type “pointer to T1 ” to the type “pointer to T2 ” (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified. [Note: See also 7.3.11 for more details of pointer conversions. —end note]
Wenn ich das richtig verstehe, sollte hier mit etwas guten Willen "back to its original type yields the original pointer value" greifen und es ist damit nicht mal "Implementation Defined" oder sowas sondern sogar standardkonform. Letzteres überrascht mich ehrlich gesagt sogar selbst.
Oh wow, mich auch :-O Danke <3
Bezüglich Punkt 3: Jede Implementierung kann eigene Erweiterungen definieren. Natürlich gibt es Bibliotheken oder Codegeneratoren (z.B. GLEW und GLAD für OpenGL) die für offiziell veröffentlichte Erweiterungen Funktionssignaturen erstellen und entsprechende Casts generieren können. Trotzdem existieren diese Casts natürlich. Genauso wie wenn du einen eigenen Vulkan-Loader schreibst oder ein Vulkan-Backend.
Darum probiere ich ja gerade anderes Design aus, damit solche Casts gar nicht erst existieren ;)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten