Default-Initialisierung eines void* mit -1



  • Oder ich nehme einen weiteren Template Parameter dazu, der den Typ des ungültigen Wertes bestimmt:

    template<typename ObjectType, typename InvalidObjectType, InvalidObjectType InvalidObjectValue>
    class
    {
     ...
    };
    

    Trotzdem ist das Ganze iwie merkwürdig. Warum geht das mit C++17 plötzlich nicht mehr?



  • Es ist bei C++17 wohl nur noch nullptr als direkte Adresszuweisung (als Template-Parameter) erlaubt.

    Der MSVC nimmt es aber weiterhin (nur CLANG und GCC nicht): Godbolt Code.



  • Eien Pointer kannst du dir mit memcpy erzeugen:

    void* make_invalid_ptr() {
        void *ptr;
        uintptr_t i = -1;
        std::memcpy(&ptr, &i, sizeof ptr);
        return ptr;
    }
    

    (ungetestet)



  • Aber nicht als Template-Parameter!



  • @DocShoe sagte in Default-Initialisierung eines void* mit -1:

    Trotzdem ist das Ganze iwie merkwürdig. Warum geht das mit C++17 plötzlich nicht mehr?

    Der Standard sagt offenbar schon länger, dass u.a. das Resultat von reinterpret_cast keine Constant Expression ist. Scheinbar waren die Compiler vorher etwas toleranter in der Auslegung.

    Ich habe jetzt nach einiger Sucherei auch keine Ein-Zeilen-Lösung gefunden und würde das selbst wahrscheinlich so lösen:

    #include <memory>
    #include <cstdint>
    
    using HANDLE = void*;
    
    template <typename ObjectType>
    struct SharedObjectTraits;
    
    template <>
    struct SharedObjectTraits<HANDLE>
    {
        static HANDLE invalid_value;
    };
    
    HANDLE SharedObjectTraits<HANDLE>::invalid_value = reinterpret_cast<HANDLE>(std::intptr_t{ -1 });
    
    template <typename ObjectType>
    class SharedObject
    {
    	struct Holder
    	{
    		ObjectType Object = SharedObjectTraits<ObjectType>::invalid_value;
    	};
    	std::shared_ptr<Holder> Holder_;
    	
    public:
    	SharedObject() = default;
    };
    
    using SharedWin32Handle = SharedObject<HANDLE>;
    
    int main() 
    {
    	SharedWin32Handle test;
    	return 0;
    }
    

    Die Traits-Klasse hilft, die Anzahl der notwenigen Template-Parameter zu reduzieren (finde ich beser zu lesen, da es weniger Code-Rauschen erzeugt). Man kann aber auch einen "Invalid Value"-Typen mit so einem Static Member als Template-Parameter übergeben, wenn dir das sinnvoller erscheint.

    Der Ansatz ist insofern auch besser, als dass er generell nicht auf Constant Expressions angewiesen ist, was die ungültigen Werte angeht. Vielleicht hat man es ja auch mal mit einem ObjectType zu tun, wo völlig klar ist, dass man den Wert nicht durch einen constexpr-Ausdruck schieben kann. Z.B. wenn invalid_value aus einer Bibliothek kommt und man nur via extern-Variable darauf zugreifen kann.

    Edit: invalid_value muss natürlich ein static Member sein.



  • Danke Finnegan, aber mit type traits funktioniert das leider nicht. Die WINAPI ist da leider inkonsistent, manche Funktionen, die ein HANDLE als Rückgabewert haben, liefern 0 (nullptr) zurück, andere -1 (INVALID_HANDLE_VALUE). Den ungültigen Wert per type trait zu bestimmen geht nicht, weil ich anhand des HANDLE nicht bestimmen kann, welcher der beiden Werte jetzt gebraucht wird.



  • @DocShoe sagte in Default-Initialisierung eines void* mit -1:

    Danke Finnegan, aber mit type traits funktioniert das leider nicht. Die WINAPI ist da leider inkonsistent, manche Funktionen, die ein HANDLE als Rückgabewert haben, liefern 0 (nullptr) zurück, andere -1 (INVALID_HANDLE_VALUE). Den ungültigen Wert per type trait zu bestimmen geht nicht, weil ich anhand des HANDLE nicht bestimmen kann, welcher der beiden Werte jetzt gebraucht wird.

    Dann übergebe den "Invalid Value"-Typen einfach per Template-Parameter:

    template <typename ObjectType, typename InvalidValueType>
    class SharedObject
    {
        ...
        ObjectType Object = InvalidValueType::invalid_value;
    };
    ...
    
    using SharedWin32Handle = SharedObject<HANDLE, SharedObjectTraits<HANDLE>>; // oder welchen Typen auch immer:
    using SharedWin32Handle2 = SharedObject<HANDLE, InvalidValueType2>; 
    using SharedWin32Handle3 = SharedObject<HANDLE, InvalidValueType3>; 
    

    Praktisch ist auch, dass man in der Klasse, die man als zusätzlichen Template-Parameter übergibt (oder als Traits-Klasse definiert), z.B. auch den Code unterbringen kann, um das Handle wieder freizugeben. Ich vermute mal da gibt es in Win32 auch vercheidene Funtionen für, je nachem, was für eine Art Handle es ist (?).



  • Und da beißt sich die Katze wieder in den Schwanz:
    Code auf C++Shell

    Hat den Fehler jetzt aus der Holder-Klasse in die Type-Traits Klasse verschoben



  • @DocShoe sagte in Default-Initialisierung eines void* mit -1:

    Und da beißt sich die Katze wieder in den Schwanz:
    Code auf C++Shell

    Hat den Fehler jetzt aus der Holder-Klasse in die Type-Traits Klasse verschoben

    Ich hatte in meiner Traits-Lösung das hier verlinkt (unter "so lösen").

    Du kannst nicht implizit von int nach void* konvertieren. Du muss das schon explizit machen.

    Und btw.: Es sind zwei Fehler hier. Einmal die implizite Konvertierung und dann (was mich auch überrascht hat) dass reinterpret_cast (die naheliegende Lösung für die Konvertierung) nicht im constexpr-Kontext erlaubt ist.



  • @Finnegan

    Jau, geht 🙂
    Jetzt muss ich mir mal Zeit nehmen und gucken, ob sich das so umsetzen lässt.



  • @Finnegan sagte in Default-Initialisierung eines void* mit -1:

    Praktisch ist auch, dass man in der Klasse, die man als zusätzlichen Template-Parameter übergibt (oder als Traits-Klasse definiert), z.B. auch den Code unterbringen kann, um das Handle wieder freizugeben. Ich vermute mal da gibt es in Win32 auch vercheidene Funtionen für, je nachem, was für eine Art Handle es ist (?).

    Ja, für sowas habe ich Allocator-Klassen, die als Template Parameter übergeben werden. Tun hier aber nichts zur Sache, also habe ich sie ausgelassen.



  • @DocShoe sagte in Default-Initialisierung eines void* mit -1:

    @Finnegan sagte in Default-Initialisierung eines void* mit -1:

    Praktisch ist auch, dass man in der Klasse, die man als zusätzlichen Template-Parameter übergibt (oder als Traits-Klasse definiert), z.B. auch den Code unterbringen kann, um das Handle wieder freizugeben. Ich vermute mal da gibt es in Win32 auch vercheidene Funtionen für, je nachem, was für eine Art Handle es ist (?).

    Ja, für sowas habe ich Allocator-Klassen, die als Template Parameter übergeben werden. Tun hier aber nichts zur Sache, also habe ich sie ausgelassen.

    Macht es vielleicht Sinn, invalid_value zu einem static Member dieser Klassen zu machen? Nur so als Anregung - das könnte den Code etwas kompakter machen und einiges an Boilerplate reduzieren. Welcher Handle-Wert ungültig ist, hängt doch sicher an den Funktionen, mit denen man das Objekt, auf welches das Handle verweist, erstellt, a.k.a "Allokations"-Funktionen, wenn ich das richtig verstehe (?). Das klingt für mich erstmal so, als ob invalid_value vom Design her gut in diese Allocator-Klassen passen würde.



  • @Finnegan

    Geht sogar noch einfacher 🙂
    Ich habe ja vorhin die Allocator-Klasse erwähnt, die für die Freigabe des gehaltenen Objekts verantwortlich ist. Die bekommt jetzt einfach noch ne statische Methode, die den ungültigen Objekttyp zurückgibt.
    Im ersten Ansatz sähe das jetzt so aus:

    #include <memory>
    
    struct HandleAllocator
    {
       static void release( HANDLE object )
       {
          CloseHandle( object );
       }
    
       static HANDLE invalid_object_value()
       {
          return INVALID_HANDLE_VALUE;
       }
    };
    
    template<typename ObjectType, typename Allocator>
    class SharedObject
    {
       struct Holder
       {
          ObjectType Object = Allocator::invalid_object_value();
    
          Holder() = default;
          Holder( ObjectType object )
          {
             Object = object;
          }
    
          ~Holder()
          {
             Allocator::release( Object );
          }
       };
       std::shared_ptr<Holder> Holder_;
       ...
    };
    
    using Win32SharedHandle = SharedObject<HANDLE, HandleAllocator>; 
    
    int main()
    {
       Win32SharedHandle sh;
    }
    


  • @DocShoe sagte in Default-Initialisierung eines void* mit -1:

    @Finnegan

    Geht sogar noch einfacher 🙂
    Ich habe ja vorhin die Allocator-Klasse erwähnt, die für die Freigabe des gehaltenen Objekts verantwortlich ist. Die bekommt jetzt einfach noch ne statische Methode, die den ungültigen Objekttyp zurückgibt.

    Finde ich sogar noch besser als Funktion. Flexibler und simplere Syntax.

    P.S.: Wenn du die Funktion jetzt übrigens noch constexpr machst, dann schliesst sich der Kreis wieder und wir können die Diskussion von vorne beginnen... SCNR 😛



  • @Finnegan sagte in Default-Initialisierung eines void* mit -1:

    P.S.: Wenn du die Funktion jetzt übrigens noch constexpr machst, dann schliesst sich der Kreis wieder und wir können die Diskussion von vorne beginnen... SCNR 😛

    Ich hab´ Zeit 😉


Anmelden zum Antworten