Search code examples
c++windowsqtcastingqdatastream

Why do I need reinterpret_cast from `long &` to `int &` where both are 32-bit (Windows LLP64)?


I am working with Qt in a cross-platform environment. And we are running into the following problem: On Windows, both int and long int are 32-bit integers; on 64-bit MacOS and Linux, int is 32-bit and long int is 64 bit (see https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models).

So, cross-platform libraries tend to provide their own fixed-bit typedefs. On Windows, Qt defines quint32 to be a unsigned int and does not use unsigned long integers. Another library defines its Uint32 however to be unsigned long. So, both are in fact 32-bit unsigned integers but have a different primitive data-type.

Now, it happens, that we try to use QDataStream serialization which is defined for quint32 with Uint32 data and to our surprise (or not), Visual C++ complains about the QDataStream operators not being defined for unsigned long which is true because Qt uses the almost equivalent unsigned int instead.

OK, the workaround is to provide

#ifdef Q_OS_WIN
inline QDataStream & operator >> (QDataStream & stream, Uint32 & value)
{ return stream >> reinterpret_cast<quint32 &>(value); }

inline QDataStream & operator << (QDataStream & stream, Uint32 value)
{ return stream << quint32(value); }
#endif // def Q_OS_WIN

My question is: Why do I need to reinterpret_cast? I would feel way more comfortable with a static_cast given that to my understanding the data-types are in fact the same. Here be dragons?


Solution

  • int and long are different data types, even if they happen to have the same properties otherwise.

    Your operator>> causes undefined behaviour due to strict aliasing violation; the correct way to write the code would be:

    inline QDataStream & operator >> (QDataStream & stream, Uint32 & value)
    { 
         quint32_t v;
         stream >> v;
         value = v;
         return stream;
    }
    

    (Note: I'm assuming the QDataStream>> has the same property as C++11 istream>> in that it sets value to 0 on read failure; if not then you'll need to include a branch so that value = v; is not executed if read failed).

    You can use static_cast for the operator<< since it is a value transformation. In fact you should be able to omit that operator<< entirely.

    The problems you are lamenting in your question are a corollary of projects making up their own typedefs instead of using the standardized ones. Unfortunately it is just something you have to deal with until such time as the projects decide to move to standardized types. If both had used uint32_t then this problem wouldn't exist.