sm::flags
Boolean flags
#include <sm/flags>
Header file: <sm/flags>.
Table of Contents
Summary
A Boolean flags class. sm::flags is for Boolean state or options in your programs. It’s as compact as using a C-style approach (integer types, preprocessor definitions and bit-wise operations), but it has a semantically meaningful C++ syntax. Although you can store state in std::bitset, there is no way to refer to elements of the state in terms of an enum without ugly use of static casts.
This code was adapted from an idea in the Vulkan code base via this gist which makes it possible to have neat, meaningful and efficient code like:
bool ready = fl.test (core::engine::ready);
Usage
The flags class is templated on type E which is required to be an enumerated class:
template <typename E> requires std::is_enum_v<E>
struct flags
{
using I = std::underlying_type_t<E>;
// ...
The underlying type of E is whatever integer type was chosen for the class (the type after the ‘:’ in the enum class).
Quick start
You generally create your own enum class to use with a flags instance. Here is one which uses uint64_t as the underlying type:
enum class myflags : uint64_t { // up to 64 flags are possible with this underlying type
flag_one, // Name the class and flags in some way that is right for your application
flag_two // It is not necessary to be explicit about the values of each flag
};
Important: It is up to you to ensure that your enum class does not contain more flags than there are bits in the underlying type! There’s no way that I know of for the compiler to test this.
The size of the underlying type determines how much storage an instance of the flags class will use. In this case, it’s 64 bits or 8 bytes.
Now you can create an object of type sm::flags<>:
sm::flags<myflags> fl;
By default, all flags here will be 0 or false.
Set a flag on fl to true:
fl.set (myflags::flag_one);
Test the flag is set:
bool flag_one_is_set = fl.test (myflags::flag_one);
Construct
The no-args constructor sets all flags to false
sm::flags<myflags> fl; // test anything and get false
You can also construct with a single enumerated flag
sm::flags<myflags> fl (myflags::flag_one); // initialized with only flag_one set.
or with an integer specifying the initial state:
uint64_t istate = 0x1;
sm::flags<myflags> fl (istate); // initialized with only flag_one set.
Lastly, you can construct with another flags object:
sm::flags<myflags> fl1;
fl1.set (myflags::flag_one);
sm::flags<myflags> fl2 (fl1);
Set flags
You can set a single flag as above with fl.set (myflags::flag_one).
If you need to clear a flag (i.e. set it to false) you can either call set with an additional argument:
fl.set (myflags::flag_one, false);
or you can use the reset method (the flags methods are chosen to be similar to those in std::bitset):
fl.reset (myflags::flag_one);
You can reset all the flags with fl.reset().
You can also set several flags to true at once with an initializer list:
fl.set ({myflags::flag_one, myflags::flag_two});
This can also be called with a value argument:
fl.set ({myflags::flag_one, myflags::flag_two}, false);
You can flip (i.e. toggle) a flag:
fl.flip (myflags::flag_one);
Test flags
The flags::test method allows you to query a flag. For a single flag it’s just fl.test (myflags::flag_two) which returns a bool.
You can test several flags in one call to test with an initializer list:
bool flags_one_AND_two_set = fl.test ({myflags::flag_one, myflags::flag_two});
You can also use the alias flags::all_of which returns true if all of the flags in the list are true:
bool flags_one_AND_two_set = fl.all_of ({myflags::flag_one, myflags::flag_two});
To test whether any of a list of flags is set, use flags::any_of:
bool flags_one_OR_two_set = fl.any_of ({myflags::flag_one, myflags::flag_two});
You can also test if any flag is set:
bool any_set = fl.any();
or if no flag is set:
bool none_set = fl.none();
You can get a count of the number of flags set, returned in the underlying type for the enum class:
uint64_t number_set = fl.count();
You can also get the flags state in the underlying type:
uint64_t flags_state = fl.get();
Writing a default settings function
Often, flags are a member of a class, and you want a set of default flags to be set when the class is instantiated. This can be achieved with a constexpr function for maximum runtime efficiency.
enum class myflags : uint8_t { one, two, three, four };
struct A // A class with flags
{
// Set default flags for this class ('two' and 'three' are true)
constexpr sm::flags<myflags> default_flags()
{
sm::flags<myflags> f;
f.set (myflags::one, false);
f.set (myflags::two, true);
f.set (myflags::three, true);
f.set (myflags::four, false);
return f;
}
sm::flags<myflags> fl = default_flags();
};
Output
You can stream your flags object to stdout:
enum class myflags : uint8_t { one, two, three, four };
std::cout << sm::flags<myflags>{myflags::one} << std::endl;
std::cout << sm::flags<myflags>{myflags::three} << std::endl;
This gives output:
00000001b
00000100b
The output is a binary string representation, with ‘1’ and ‘0’ characters followed by a ‘b’. The number of digits depends on the size of the underlying type (in this case, there are 8 bits in uint8_t). The binary string shows the first flag as the least significant bit to the right. For the example above, we get 00000001b for myflags::one and 00000100b for myflags::three
Operations on flags
As alternatives to flags::test and flags::set, you can apply Boolean operators with enumerated class arguments to your sm::flags objects:
// Set
fl |= myflags::one;
fl |= myflags::two;
// Flip
fl ^= myflags::one;