I32 zu I64 Wrapperklassen Konvertierungsfehler



  • Hi, ich habe zwei Klassen I32 u. I64 die Wrapper um die primitiven Typen int32_t bzw. int64_t sind. Allerdings funktioniert mit diesen Wrappern die Zuweisung eines I32 zu einem I64 nicht trotz des Assignment + Konvertierungsoperators. Ich hätte erwartet dass der Kompiler hier eine impliziete Umwandlung macht? Mit den primitiven Typen klappt die Zuweisung dagegen.

    class I32
    {
    public:
    	I32() {};
    	I32(int32_t value) : _value(value) {};
    
    	I32& operator=(int32_t value) { _value = value; return *this; }
    	I32& operator=(const I32& value) { _value = value._value; return *this; }
    	operator int32_t() const { return _value; }
    
    private:
    	int32_t _value;
    };
    
    class I64
    {
    public:
    	I64() {};
    	I64(int64_t value) : _value(value) {};
    
    	I64& operator=(int64_t value) { _value = value; return *this; }
    	I64& operator=(const I64& value) { _value = value._value; return *this; }
    	operator int64_t() const { return _value; }
    
    private:
    	int64_t _value;
    };
    
    int main()
    {
    	// Funktioniert:
    	//int32_t i32 = 0;
    	//int64_t i64 = i32;
    	// Funktioniert nicht:
    	I32 i32 = 42;
    	I64 i64 = i32;
    	if (i64 == i32)
    	{
    		printf("Equals\n");
    	}
    	else
    	{
    		printf("Not equals\n");
    	}
    	return 0;
    }
    
    error: no viable conversion from 'I32' to 'I64'
    I64 i64 = i32;
    


  • Zunächst einmal kommt der Assignment-Operator hier nicht zum tragen, da es sich trotz der zuweisungs-ähnlichen Syntax bei I64 i64 = i32; um eine Initialisierung, genauer um eine Copy Initialization handelt, bei der nur Konstruktoren und implizite Konvertierungen berücksichtigt werden.

    Das Regelwerk für implizite Konvertierungen ist etwas umfangreicher, der hier wichtige Punkt ist jedoch dieser hier:

    Implicit conversion sequence consists of the following, in this order:

    1. zero or one standard conversion sequence;
    2. zero or one user-defined conversion;
    3. zero or one standard conversion sequence.

    Es wird nur maximal eine benutzerdefinierte Konvertierung berücksichtigt.

    Damit dieser Code nun

    I64 i64 = i32;
    

    mit deinen Konstruktoren und Kovertierungsoperatoren funktioniert, wären allerdings zwei Konvertierungen notwendig: Einmal via I32::operator int32_t() zu einem int32_t und dann wieder via den Konstruktor I64(int64_t value) und einer standard conversion sequence in einen I64.

    i32 ist salopp gesagt nicht "in einem Schritt" in einen I64 konvertierbar, deshalb bekommst du die Fehlermeldung.

    Die Lösung ist, die Konvertierung in einem Schritt zu ermöglichen: Entweder über einen I32::operator I64() oder über einen Konstruktor I64(const I32& value) mit dem sich die Variable ohne Umwege direkt initialisieren lässt.

    Zusatz: Ich würde übrigens die Konstruktor-Variante empfehlen. Mit dieser kannst du dir nämlich die Assignment-Operatoren komplett sparen. Z.B. ist dein I32& operator=(const I32& value) ohnehin nichts anderes als der vom Compiler erzeugte Default-Assignment-Operator und dein I32& operator=(int32_t value) wird vom Konstruktor I32(int32_t value) automatisch abgedeckt: int32_t-Argumente, die man dem compiler-generierten I32& operator=(const I32&) übergibt, werden implizit via I32(int32_t value) in einen I32 konvertiert.

    Der operator int32_t() const hingegen macht durchaus Sinn, wenn man i32 z.B. einer Funktion übergeben möchte, die einen int erwartet.



  • Danke schon mal für deine Antwort. Das mit der doppelten Umwandlung habe ich schon befürchtet.

    Ich frage mich nur gerade was der einfachste Weg wäre Wrapper-Klassen für eingebaute Basistypen zu schreiben die sich genauso wie diese verhalten bzw. interagieren können ohne dafür dutzende Konstruktoren und Operatoren für alle möglichen Umwandlungen schreiben zu müssen.

    Das fühlt sich einfach falsch an.
    Auf die paar Assignment Operatoren kommt es dann übrigens auch nicht mehr an :).

    Gibt es evtl. ein Framework was solche Wrapper Typen verwendet wo man sich mal inspirieren lassen könnte?



  • @Enumerator sagte in I32 zu I64 Wrapperklassen Konvertierungsfehler:

    ... Ich frage mich nur gerade was der einfachste Weg wäre Wrapper-Klassen für eingebaute Basistypen zu schreiben die sich genauso wie diese verhalten bzw. interagieren können ohne dafür dutzende Konstruktoren und Operatoren für alle möglichen Umwandlungen schreiben zu müssen. ...

    Du kannst SFINAE nutzen um Konstruktor und Assignment operatoren für 'kompatible' Typen bereitzustellen. Beispiel C++11 (ungetestet):

    #include <type_traits>
    
    class wrapper
    {
        using value_type = int;
    
        value_type value;
    
    public:
    
        // SFINAE Constructor fuer alle konvertierbaren Typen
    
        template <  typename T,
                    typename std::enable_if<std::is_convertible<T, value_type>::value, int>::type = 0>
        wrapper(const T& arg)
            : value(arg)
        {}
    
        // SFINAE Assignment fuer alle konvertierbaren Typen
    
        template <  typename T,
                    typename std::enable_if<std::is_convertible<T, value_type>::value, int>::type = 0>
        wrapper& operator = (const T& arg)
        {
            value = arg;
    
            return *this;
        }
    
        operator value_type () const
        {
            return value;
        }
    };
    

Log in to reply