Games for Windows and the DirectX SDK blog

Technical tips, tricks, and news about game development for Microsoft platforms including desktop, Xbox, and UWP


Project maintained by walbourn Hosted on GitHub Pages — Theme by mattgraham
Home | Posts by Tag | Posts by Month

Modern C++ Bitmask types

github

Chuck Walbourn -

Bitmask flags are a ubiquitous pattern in C & C++ code. In the ‘old days’, they were typically defined as:

#define FLAGS_NONE             0x0
#define FLAGS_DO_A_THING       0x1
#define FLAGS_DISABLE_A_THING  0x2
#define FLAGS_OPTIONAL_FEATURE 0x4

HRESULT MyFunction(int flags);

Since C/C++ preprocessor defines tend to have a lot of potential hazards, over time developers have moved to using enum:

enum FLAGS_FOR_MYFUNCTION
{
    FLAGS_NONE             = 0x0,
    FLAGS_DO_A_THING       = 0x1,
    FLAGS_DISABLE_A_THING  = 0x2,
    FLAGS_OPTIONAL_FEATURE = 0x4
};

HRESULT MyFunction(int flags);

With C++11 you can also provide an underlying type of the enum so you can use unsigned long instead of int without any signed/unsigned mismatch warnings:

enum FLAGS_FOR_MYFUNCTION : unsigned long
{
    FLAGS_NONE             = 0x0,
    FLAGS_DO_A_THING       = 0x1,
    FLAGS_DISABLE_A_THING  = 0x2,
    FLAGS_OPTIONAL_FEATURE = 0x4
};

HRESULT MyFunction(unsigned long flags);

This still leaves the use of flags ‘loosely typed’ since if you had a second set of (possibly unrelated) flags you can easily write:

HRESULT MyFunction(FLAGS_DO_A_THING | UNRELATED_FLAGS_FEATURE);

Modern C++ recommends solving this with Bitmask flags per the standard (section 17.5.2.1.13). DirectX 11 used the older loosely typed DWORD (a.k.a unsigned long) plus enum flags, but DirectX 12 adopts this new typed bitmask flags pattern. Therefore, I’ve updated my GitHub libraries to use the pattern as well, and this does have some implications for existing code.

The Modern C++ recommendation is to change our example as follows:

enum FLAGS_FOR_MYFUNCTION
{
    FLAGS_NONE             = 0x0,
    FLAGS_DO_A_THING       = 0x1,
    FLAGS_DISABLE_A_THING  = 0x2,
    FLAGS_OPTIONAL_FEATURE = 0x4
};

constexpr inline T operator~ (T a) { return static_cast<T>( ~static_cast<std::underlying_type<T>::type>(a) ); }
constexpr inline T operator| (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b) ); }
constexpr inline T operator& (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b) ); }
constexpr inline T operator^ (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b) ); }
constexpr inline T& operator|= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b) ); }
constexpr inline T& operator&= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b) ); }
constexpr inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }

HRESULT MyFunction(FLAGS_FOR_MYFUNCTION flags);

This provides the various bit-wise operators so you can call:

hr = MyFunction(FLAGS_DISABLE_A_THING | FLAGS_DO_A_THING);

C++11 introduced the idea of strongly typed enumerations in the form of enum class which prevents the implicit conversion of an enum to it’s underlying type as well as scoping the enum members to it’s namespace. While you can use it for these Bitmask types, it does make bit-testing them a little less intuitive:

// This compiles if you used enum, but not if you use enum class
if (flags & FLAGS_DISABLE_A_THING)
    ...

// For an enum class, you have to use:
if ((flags & FLAGS_FOR_MYFUNCTION::FLAGS_DISABLE_A_THING) == FLAGS_FOR_MYFUNCTION::FLAGS_DISABLE_A_THING)
    ...

In DirectX 12, DirectX Tool Kit for DX11 / DX12, DirectXTex, and DirectMesh they all make use of a macro from winnt.h that defines the operators for a given type:

enum FLAGS_FOR_MYFUNCTION
{
    FLAGS_NONE             = 0x0,
    FLAGS_DO_A_THING       = 0x1,
    FLAGS_DISABLE_A_THING  = 0x2,
    FLAGS_OPTIONAL_FEATURE = 0x4
};

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec"
#endif

DEFINE_ENUM_FLAG_OPERATORS(FLAGS_FOR_MYFUNCTION)

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec"
#endif

HRESULT MyFunction(FLAGS_FOR_MYFUNCTION flags);

With the Windows 10 SDK (19041) and clang v10, there are some conformance issues with DEFINE_ENUM_FLAG_OPERATORS that generates -Wdeprecated-dynamic-exception-spec so I suppress those in my libraries when using this macro.

Client code impact

What does this all actually mean for a developer using the libraries?

Well, for one thing the literal 0 is not a valid flags value.

// This won't build with the June 2020 release or later of DirectXTex
hr = DirectX::LoadFromDDSFile("texture.dds", 0, nullptr, image);

// Instead you must use the default enum value:
hr = DirectX::LoadFromDDSFile("texture.dds", DDS_FLAGS_NONE, nullptr, image);

Also, anywhere you ‘build-up’ a flags setting, you need to use the correct type instead of DWORD.

// This used to build but now does not.
DWORD flags = DDS_FLAGS_NONE;
if (somesetting)
    flags |= DDS_FLAGS_FORCE_RGB;

// The modern C++ way to write it is:
DDS_FLAGS flags = DDS_FLAGS_NONE;
if (somesetting)
    flags |= DDS_FLAGS_FORCE_RGB;

DirectXTex in particular has numerous sets of flags, so this improves type-safety so you can’t use the incorrect flags for a particular context:

// This used to build and didn't do what you expected...
// It now results in a compile-time error.
hr = DirectX::LoadFromDDSFile("texture.dds", WIC_FLAGS_ALLOW_MONO, nullptr, image);

That said, there are a few specific cases where I was a little ‘too clever’ in my overlapping of loosely typed flags. For example, the TEX_FILTER_POINT, TEX_FILTER_LINEAR, TEX_FILTER_CUBIC, and TEX_FILTER_FANT flags were designed to exactly match WIC_FLAGS_FILTER_POINT, WIC_FLAGS_FILTER_LINEAR, WIC_FLAGS_FILTER_CUBIC, and WIC_FLAGS_FILTER_FANT. Therefore there are a few specific operator| overloads defined to handle these cases.

// This used to build and work before
DWORD filterFlags = TEX_FILTER_CUBIC;
hr = DirectX::LoadFromWICFile("texture.dds", filterFlags, nullptr, image);

// As of DirectXTex June 2020 or later, it needs to be:
TEX_FILTER filterFlags = TEX_FILTER_CUBIC;
hr = DirectX::LoadFromWICFile("texture.dds", WIC_FLAGS_NONE | filterFlags, nullptr, image);

Note that I don’t define every possible permutation of the operator| for these cross-type cases. The ‘destination’ type must be the first flag type. IOW: filterFlags | WIC_FLAGS_NONE will not compile. I also don’t define all the other bit-wise operators for these combo cases, even though each flag type does support them.