Skip to content

Bitwise operation helpers

Duncan Horn edited this page Jul 16, 2020 · 4 revisions

Bitwise operation helpers

WIL defines a number of helper macros in wil/common.h to improve readability and reduce the error rate of bitwise operations.

The macros improve readability by expressing the operation concisely: Flags and variables are specified only once per macro invocation, and the macro name describes the nature of the operation.

The macros reduce errors by capturing complex bit operations into a single step. Single-flag operations validate at compile time that exactly one flag is specified.

For example,

// instead of: days = days & ~DaysOfWeek::Monday;
WI_ClearFlag(days, DaysOfWeek::Monday);

// instead of: if ((days & (DaysOfWeek::Monday | DaysOfWeek::Tuesday)) == (DaysOfWeek::Monday | DaysOfWeek::Tuesday))
if (WI_AreAllFlagsSet(days, DaysOfWeek::Monday | DaysOfWeek::Tuesday))

Flag manipulation

In the following table:

  • var is a variable.
  • flag is a compile-time constant that is a power of two (a single flag). This requirement is enforced at compile time.
  • flags, mask, and newFlags are a run-time value or a compile-time constant. It can consist of zero or more flags.
  • cond is a boolean condition expression.

The macro modifies the value in var as indicated.

Macro Operation
WI_SetFlag(var, flag); Set a single bit.
WI_SetFlagIf(var, flag, cond); Set a single bit if cond is true.
WI_SetAllFlags(var, flags); Set zero or more bits.
WI_ClearFlag(var, flag); Clear a single bit.
WI_ClearFlagIf(var, flag, cond); Clear a single bit if cond is true.
WI_ClearAllFlags(var, flags); Clear zero or more bits.
WI_UpdateFlag(var, flag, cond); Set a single bit if cond is true, or clear the bit if cond is false.
WI_UpdateFlagsInMask(var, mask, newFlags); Change the flags specified by mask to match the corresponding bits in newFlags.
WI_ToggleFlag(var, flag); Toggle (XOR) a single bit.
WI_ToggleAllFlags(var, flags); Toggle (XOR) zero or more bits.

Flag inspection

In the follow table:

  • val is an expression.
  • flag is a compile-time constant that is a power of two (a single flag). This requirement is enforced at compile time.
  • flags, mask, and newflags are a run-time value or a compile-time constant. It can consist of zero or more flags.

The macro tests the value val as indicated, returning true if the condition is met or `false if the condition is not met.

Macro Condition
WI_IsFlagSet(val, flag) The bit specified by flag is set in val.
WI_IsAnyFlagSet(val, flags) Any bit specified by flags is set in val.
WI_AreAllFlagsSet(val, flags) All bits specified by flags are set in val.
WI_IsFlagClear(val, flag) The bit specified by flag is not set in val.
WI_IsAnyFlagClear(val, flags) Any bit specified by flags is not set in val.
WI_AreAllFlagsClear(val, flags) No bits specified by flags are set in val.
WI_IsSingleFlagSet(val) Exactly one bit is set in val.
WI_IsSingleFlagSetInMask(val, mask) Exactly one bit specified by mask is set in val.
WI_IsClearOrSingleFlagSet(val) At most one bit is set in val.
WI_IsClearOrSingleFlagSetInMask(val, mask) At most one bit specified by mask is set in val.

Examples

enum class DaysOfWeek
{
    None      = 0x0000,
    Sunday    = 0x0001,
    Monday    = 0x0002,
    Tuesday   = 0x0004,
    Wednesday = 0x0008,
    Thursday  = 0x0010,
    Friday    = 0x0020,
    Saturday  = 0x0040,
};
DEFINE_ENUM_FLAG_OPERATORS(DaysOfWeek);

extern DaysOfWeek GetAvailableDays();

DaysOfWeek days = DaysOfWeek::Sunday;

WI_SetFlag(days, DaysOfWeek::Saturday);
// result: days = Sunday | Saturday

WI_ClearFlag(days, DaysOfWeek::Sunday);
// result: days = Saturday

WI_SetAllFlags(days, DaysOfWeek::Monday | DaysOfWeek::Tuesday);
// result: days = Monday | Tuesday | Saturday

WI_SetFlagIf(days, DaysOfWeek::Friday, false);
// result: days = Monday | Tuesdy | Saturday (unchanged)

WI_ToggleFlag(days, DaysOfWeek::Monday);
// result: days = Tuesday | Saturday

WI_UpdateFlag(days, DaysOfWeek::Monday, false);
// result: days = Tuesday | Saturday

// Sets Monday and clears Tuesday. Wednesday is not affected.
WI_UpdateFlagsInMask(days, DaysOfWeek::Monday | DaysOfWeek::Tuesday,
                           DaysOfWeek::Monday | DaysOfWeek::Wednesday);
// result: days = Monday | Saturday

if (WI_IsFlagSet(days, DaysOfWeek::Sunday)) ... // false

if (WI_AreAllFlagsSet(days, GetAvailableDays()) ...

if (WI_AreAllFlagsClear(days, GetAvailableDays()) ...

// Test whether exactly one flag is set.
if (WI_IsSingleFlagSet(days)) ... // false

The following uses will not compile.

WI_SetFlag(days, DaysOfWeek::None); // error

// Use WI_SetAllFlags instead.
WI_SetFlag(days, DaysOfWeek::Thursday | DaysOfWeek::Friday); // error

// Use WI_SetAllFlags instead.
WI_SetFlag(days, GetAvailableDays()); // error

if (WI_IsFlagSet(days, DaysOfWeek::None)) // error

// Error.
// Use WI_AreAllFlagsSet or WI_IsAnyFlagSet depending on what you actually want.
if (WI_IsFlagSet(days, DaysOfWeek::Sunday | DaysOfWeek::Saturday)) // error

Other helpers

WI_EnumValue

The WI_EnumValue macro returns an unsigned integer of the same width and numeric value as the provided enum value. This is handy when validating that an enum's fields match some other set of integers.

enum class DropEffects
{
    None = 0x0000,
    Copy = 0x0001,
    Move = 0x0002,
    Link = 0x0004,
};

static_assert(WI_EnumValue(DropEffects::None) == DROPEFFECT_NONE);
static_assert(WI_EnumValue(DropEffects::Copy) == DROPEFFECT_COPY);
static_assert(WI_EnumValue(DropEffects::Move) == DROPEFFECT_MOVE);
static_assert(WI_EnumValue(DropEffects::Link) == DROPEFFECT_LINK);

WI_StaticAssertSingleBitSet

Forces a compiler error if the value is not an exact power of two. Returns the original value. This is typically used to build other bit manipulation macros.

if (effect & WI_StaticAssertSingleBitSet(DropEffects::Copy))
{
   ...
}