How do you deal with tons of different same fundamental data types?

Started by
14 comments, last by Norman Barrows 8 years, 6 months ago

Let us stay simple and state I want and need to use a single definition for fundamental types with fixed dimensions. (let's forget about why do Microsoft and the C/C++ standards provides non-fixed size...)

How do you deal with the tons of the Windows data types, the VC compiler data types, and the C/C++ unfixed data types? Do you split into two parts your project, one where you code only with your own defined types (where you do not have any external library includes) and another where you have to deal with the Windows headers or other libraries?

Actually I pretty like to use the cstding fixed-size integers, but every-time a function or a structure except a Windows data type I always check for compatibility through C++-style casts (not C-style casts). It is useless to state how boring and annoying is this.

I was thinking to create an header where using tons of static_assert and check the compatibility of my types with Windows Data types and C/C++ types. But I am not sure about that, since I was to add it in the PCH after the Windows and the C/C++ standard library includes.

So, any advice how to make neat all this is accepted.

"Recursion is the first step towards madness." - "Skegg?ld, Skálm?ld, Skildir ro Klofnir!"
Direct3D 12 quick reference: https://github.com/alessiot89/D3D12QuickRef/
Advertisement

I include <cstdints>, and use the standardized fixed width types: uint64_t, uint32_t, uint16_t, uint8_t, etc...

If your implementation doesn't support that type, then that type won't be defined, afaik.

Typically, I use int or unsigned for general usage, and size_t, uint32_t, etc... to more explicitly indicate my assumptions only when I need the fixed width.

Occasionally I have to talk to other 3rd party libraries that use other formats, and in those situations I try to isolate the additional formats only to the borders of where my code needs to talk to that library.

What do you do in a piece of code where you retrieve/pass parameters to/from Windows libraries? Actually I pass my types (or just <cstint> types) and then I static_cast them from/to windows data types. But this is really annoying.

"Recursion is the first step towards madness." - "Skegg?ld, Skálm?ld, Skildir ro Klofnir!"
Direct3D 12 quick reference: https://github.com/alessiot89/D3D12QuickRef/
Write wrappers around windows calls. Inside your classes, you will include the windows files and use windows types and functions to perform actions. Outside the class, you are using your own types.

Example


// window.h

#include <windows.h>

class Window
{
    HDC windowDeviceContext;
    HWND windowHandle;
public:
    Window(const std::string& title, int width, int height);
    void SetSize(int width, int height);
    void SetTitle(const std::string& title);
    ...
};

My current game project Platform RPG

I don't deal with Win32 directly, but I do deal with some other third-party libraries with their own types (Qt, GL, and SFML for example).

In those cases, if it makes sense to do so, I do static_cast. That's the appropriate way to convert between two similar data types.

Yea, it can get slightly annoying, but in classes that deal excessively with a third-party library, sometimes it makes more sense to treat that class as an extension to that third-party library, and use that library's third-party types internally or even externally.

Every class basically falls into "My code" or "Extension of that library's code", so either my code only calls into that library in a few isolated places, or it calls into my class that wraps functionality and uses the types internally.

Keeping 'their' code separate at the edges of 'your' code is also helpful in switching to some other library, if you ever need to do that.

Windows-specific code should always be strongly decoupled/isolated from the rest of your code-base. That's just good cross-platform practice!

Only very small amounts of your game should interact with Windows (or other low-level APIs) directly, and these parts of your game should be isolated behind some nice, easy-to-use interface. The rest of your game (99% of your code) should be all nice and standard and clean and portable :)

Yes, I still try to separate my own code with code that directly interact with 3rd party libraries (like win32 APIs). I was wondering if there are some dirty tricks (even preprocessor tricks) to feed my laziness and avoid explicit static_cast et similia.

Another question: is a good practice to "undef" 3rd party data types in the project parts you do not need them? I saw that in the UE source code.

"Recursion is the first step towards madness." - "Skegg?ld, Skálm?ld, Skildir ro Klofnir!"
Direct3D 12 quick reference: https://github.com/alessiot89/D3D12QuickRef/

Another question: is a good practice to "undef" 3rd party data types in the project parts you do not need them? I saw that in the UE source code.

Don't include them in the other parts in the first place! IMHO having to attempt to undef stuff is a failing.
I've worked with quite a few engines that put "windows.h" into the PCH, which pollutes every single damn file... IMHO this is a terrible thing to do.
Keep implementations hidden smile.png

I was wondering if there are some dirty tricks (even preprocessor tricks) to feed my laziness and avoid explicit static_cast et similia.

You can make some shorthands...
MyVec4 cast( const TheirVec4& v )
{
  static_assert( sizeof(MyVec4) == sizeof(TheirVec4) );
  static_assert( offsetof(MyVec4,x) == offsetof(TheirVec4,x) );
  static_assert( offsetof(MyVec4,y) == offsetof(TheirVec4,y) );
  static_assert( offsetof(MyVec4,z) == offsetof(TheirVec4,z) );
  static_assert( offsetof(MyVec4,w) == offsetof(TheirVec4,w) );
  return *(MyVec4*)&v;
}
^^ It's good practice to always document any assumption that you make with an assertion or compile-time assertion. Here I'm documenting that it's safe to reinterpret cast these two vec4 types smile.png

As for the windows types, many of them are just typedefs, so there's no casting requried, e.g.
BYTE windows = 42;
uint8_t portable = windows;//no cast, because BYTE is an unsigned 8bit integer!

Thank you, I was not aware about offsetof macro... So, I will try to create some sort of control checks where I put the Windows headers and since cl.exe do not allows multiple pre-compiled headers, I will split all the work into two projects (which was not the initial intention because I am actually working alone on it).

"Recursion is the first step towards madness." - "Skegg?ld, Skálm?ld, Skildir ro Klofnir!"
Direct3D 12 quick reference: https://github.com/alessiot89/D3D12QuickRef/

This topic is closed to new replies.

Advertisement