Unique ID zur Compile Zeit generieren



  • @Arcoth
    Ahh, ich doof. Mit weniger Kopfweh nochmal drüber nachdenken hilft.
    Der schwerwiegendste Grund hat meiner Einschätzung nach nix damit zu tun dass rumgecastete Zeiger nicht constexpr dereferenziert werden könnten, und daher reinterpret_cast halt generell verboten ist. Das liesse sich leicht fixen indem man nur die Richtung int -> pointer verbietet.

    Viel problematischer ist aber dass der Wert des Integers der dabei entstehen soll einfach nicht sinnvoll "compile time" ermittelt werden kann. Weil der im Prinzip erst beim Linken feststeht. Linken kann man aber erst wenn man weiss was das Programm überhaupt machen soll. Und das kann man ja sehr einfach von compile-time-konstanten Integerwerten abhängig machen.

    Was dann auch gleich die Antwort darauf ist:

    Arcoth schrieb:

    Ich sehe auch nicht wirklich irgendeinen handfesten Anreiz, so eine Asymmetrie einzuführen. Die meisten Experten, vermute ich, würden auf die Strategie mit void(*)() als Typ deiner IDs hinweisen.

    Naja mit dem void(*)() kannst du halt nix machen, ausser ihn als Template Parameter zu verwenden. Aber du kannst nicht drauf switchen, keinen Hashwert daraus ausrechnen - dergleichen Dinge. Und selbst wenn du ne if-Kaskade hinschreibst wir der Compiler diese nicht gut optimieren können, da er den Wert des Zeigers ja noch nicht kennt.

    Und dann frage ich mich wozu ein void(*)() überhaupt noch gut sein soll, wenn alles was man damit anstellt eh erst zur Laufzeit ausgewertet werden kann. Dann kann man vermutlich genau so gut std::type_index nehmen. Bzw. wenn man some_key<T> in Templates als Key braucht, kann man wohl genau so gut gleich nur T als Key nehmen.


  • Mod

    hustbaer schrieb:

    @Arcoth
    Ahh, ich doof. Mit weniger Kopfweh nochmal drüber nachdenken hilft.
    Der schwerwiegendste Grund hat meiner Einschätzung nach nix damit zu tun dass rumgecastete Zeiger nicht constexpr dereferenziert werden könnten, und daher reinterpret_cast halt generell verboten ist. Das liesse sich leicht fixen indem man nur die Richtung int -> pointer verbietet.

    SIcher, aber warum sollte man pointer -> int konkret erlauben? Die WG Ausschüsse ist grundsätzlich pessimistisch eingestellt, und führen nur die Parts ein, die für ein Feature wesentlich sind. Alles andere wird dann mit entsprechenden Papers erweitert. Das strukturiert den Gesamtprozess. Es gab bisher schlicht keine motion bzgl. dieser Konvertierung. Das ist aber hier nicht der Hauptgrund, der kommt hier:

    Viel problematischer ist aber dass der Wert des Integers der dabei entstehen soll einfach nicht sinnvoll "compile time" ermittelt werden kann. Weil der im Prinzip erst beim Linken feststeht.

    Du hast Recht, der Linker führt relocation durch. Es kann unter gewissen Architekturen auch relocation zur load time notwendig sein, weshalb einem Symbol allgemein vor der Ausführung des Programs keine sinnvolle Adresse zuweisen kann.
    Aber das ist gar nicht nötig, denn schon wenn ein Zeiger via (int*)(intptr_t)ptr ausgedrückt wird, zeigt er nicht auf das Objekt, und kann nicht verwendet werden, um darauf zuzugreifen. Nicht einmal Vergleiche von zwei identisch hergeleiteten Zeigerwerten sind definiert: Weil diese Zeiger nicht nachweislich auf das selbe Objekt zeigen. Wo liegt also das Problem, zur compile time einen zufälligen Wert zuzuweisen, der für Objekte innerhalb einer TU konsistent ist? Oder ist meine Argumentation oben einfach nicht mit der Praxis kompatibel? Es kann gut sein, dass ein als reachable deklariertes Objekt dann auch durch einen solchen Ausdruck angefasst werden darf. Alles in allem ist die de facto Lage IMO in Ordnung.

    Edit: Man kann Clang dazu überreden, einen solchen scheinbar zufälligen Wert zu erzeugen, indem man ausnutzt, dass die Zweige in einer conditional expression mit Bedingung __builtin_constant_p als constant expressions übernommen werden.

    Was den Nutzen von Zeigern zur compile zeit anbelangt: Der TE hat ja gerade ein variadisches Template. Der Zeiger würde dann eben als eine Art Hash von beliebig vielen Typen fungieren. Es stimmt aber, dass man damit so gut wie nichts anstellen kann. Die nächste Frage wäre, was der TE mit diesem Mechanismus zu lösen versucht. XY halt.



  • Arcoth schrieb:

    Nicht einmal Vergleiche von zwei identisch hergeleiteten Zeigerwerten sind definiert: Weil diese Zeiger nicht nachweislich auf das selbe Objekt zeigen.

    Dafuq?
    reinterpret_cast T* a -> uintptr_t i -> T* b ist soweit ich weiss sehrwohl definiert, und zwar so dass danach a == b gelten muss.
    Oder meinst du den Fall T* a -> uintptr_t i -> U* b mit T != U?

    Arcoth schrieb:

    Wo liegt also das Problem, zur compile time einen zufälligen Wert zuzuweisen, der für Objekte innerhalb einer TU konsistent ist? Oder ist meine Argumentation oben einfach nicht mit der Praxis kompatibel? Es kann gut sein, dass ein als reachable deklariertes Objekt dann auch durch einen solchen Ausdruck angefasst werden darf. Alles in allem ist die de facto Lage IMO in Ordnung.

    Angesichts der Tatsache dass der Aufwand es zu implementieren um mehrere Grössenordnungen zu hoch wäre, finde ich es auch OK. Was aber nicht heisst dass es nicht praktisch wäre 🙂

    Arcoth schrieb:

    Die nächste Frage wäre, was der TE mit diesem Mechanismus zu lösen versucht. XY halt.

    Richtig. Das wäre interessant.


  • Mod

    hustbaer schrieb:

    Arcoth schrieb:

    Nicht einmal Vergleiche von zwei identisch hergeleiteten Zeigerwerten sind definiert: Weil diese Zeiger nicht nachweislich auf das selbe Objekt zeigen.

    Dafuq?
    reinterpret_cast T* a -> uintptr_t i -> T* b ist soweit ich weiss sehrwohl definiert, und zwar so dass danach a == b gelten muss.

    Ja, das würde ich auch sagen. Ich bin halt von einer sehr wortwörtlichen Exegese ausgegangen, aber die wäre wahrscheinlich sowieso nicht kohärent (bspw. ist das alternative Resultat entschieden negativ, wo man eher unspezifiziertes Verhalten erwarten würde). Und wenn wir vorschreiben, dass obiges funktionieren soll, dann können wir auch keinen zufälligen Wert zuweisen, da nicht über mehrere TUs einheitlich machbar. Du hast Recht. Ich berufe mich allerdings auf meinen (gerade beendeten!) Kaffeeentzug nach der Weissheitszahn-OP, der mich meiner Konzentration beraubt hat :p



  • Arcoth schrieb:

    Du hast Recht. Ich berufe mich allerdings auf meinen (gerade beendeten!) Kaffeeentzug nach der Weissheitszahn-OP, der mich meiner Konzentration beraubt hat :p

    Ich glaube wenn wir zählen wer von uns beiden in den letzten Jahren bezüglich Standard öfter daneben gelegen hat ... würde ich ziemlich alt aussehen. 😉



  • ps: Das reinterpret_cast -Verbot verhindert u.A. auch dass man offsetof standardkonform compile-time-const implementiert. Wobei ich das als einen Spezialfall ansehe der damit gut abgedeckt ist dass der Standard vorschreibt dass es offsetof zu geben hat, und die Implementierung es halt irgendwie magisch bereitstellen muss.


  • Mod

    Selbiges gilt für addressof . Es gibt sogar einen Paper Sketch, der die Konvertierung zu Zeigern auf character types zu erlauben sucht: http://apolukhin.github.io/constexpr_algorithms/reinterpret.html



  • Arcoth schrieb:

    Die nächste Frage wäre, was der TE mit diesem Mechanismus zu lösen versucht.

    Ich bau mir eine kleine Bibliothek, mit der ich SQL Queries zur Compile Zeit aus Klassen generieren kann. Dafür brauche ich einen zufälligen Compile-Time-String, den ich als Namen für mein SQL Feld nutzen kann. (Weil ich unter Umständen die selben Feld Namen in unterschiedlichen Tabellen nutzen werde) Bsp:

    SELECT Table1.Fuu AS MyUniqueIdentifier1, Table2.Fuu AS MyUniqueIdentifier2 FROM ...;
    

  • Mod

    Und wofür muss der String eine Compilezeit Konstante sein?



  • Muss er dann vermutlich eh nicht.

    Das Szenario kann man dann ja schön lösen indem man die Adresse einer statischen Membervariable des Template nimmt, diese dann (ohne constexpr) in einen uintptr_t konvertiert, und dann runtime hex-codiert. (Bzw. einfach den stream insertion operator für void* verwendet.)


  • Mod

    hustbaer schrieb:

    Muss er dann vermutlich eh nicht.

    Das Szenario kann man dann ja schön lösen indem man die Adresse einer statischen Membervariable des Template nimmt, diese dann (ohne constexpr) in einen uintptr_t konvertiert, und dann runtime hex-codiert.

    Und wozu die Konvertierung nach uintptr_t ? Kannst doch direkt auf den Zeiger sprintf 'n?



  • Ja, richtig.
    Oder stream insertion operator für void* (siehe EDIT von mir eben gerade).
    Ist mir dann auch aufgefallen 🙂

    Bzw. wenn die generierten IDs/Strings nicht "stable" zwischen verschiedenen Programmausführungen sein müssen könnte man auch einfach einen Atomic-Counter verwenden um ein function-local-static zu initialisieren. Wobei die Member-Adressen Variante vermutlich so ziemlich universell besser ist.


  • Mod

    Wenn wir aber von Tabellennamen sprechen, könnte man doch gleich die demanglete Version von typeid(x).name() verwenden, oder? Man muss schauen, dass das auch einigermaßen eindeutig ist (am besten vollständig qualifiziert).



  • Ich würde dann eher noch die "rohe" Version verwenden und dann hex-codieren - die ist garantiert eindeutig (und man kann über keine Bugs beim Demangling stolpern).
    Nur: Diese Namen können ganz schnell verflixt lange werden. Ein paar hundert Zeichen sind da nix. Normalerweise nicht das was man als Identifier in Database Statements haben möchte.


Anmelden zum Antworten