All solutions to flag sets that I've come across are sets of integers representing individual bits (often preprocessor constants or enum
s). What has always bothered me about these is that they must be combined using bitwise OR. That is, we're adding values to a set using or. A | B
reads as "A or B", not "A and B". To test if a flag is set, we use bitwise AND, equally awkward if you think about it. These operators are so well known that people don't think about them, but I'd rather use operators that better represent the action taken.
So I set out to create a simple class that I could use to pass flags into my functions. To add things to the set of flags I wanted to use +
. -
can be used to remove flags from the set.
The difficulty I have is with testing for flags. What is a good overloadable C++ operator that represents the "contains" operation? I wanted it to read "set X contains A". This is an asymmetric test by definition, that you can't reverse. The <
operator is the closest in shape to "contains", but very far in meaning. It might also create expectations: X < A
would suggest !(X >= A)
, but if X contains A, then it's not necessarily true that X is not contained in A (think e.g. in X only contains A).
So I settled for the ==
operator. X == A
means X contains A, and X != A
means it doesn't contain A. The danger now is that X == A
does not necessarily imply A == X
.
Below is the class I wrote, with a usage example. I'd like to hear suggestions for better operators to use instead of ==
. I'm also open for any comments on style, clarity, etc.
There is a class that encapsulates options. It is not used directly, but through two macros. These are necessary to create unique types such that different flag sets cannot be mixed. All constants defined through the second macro are constexpr
; adding and testing values can potentially be done at compile time.
namespace dip {
namespace detail {
template< typename E >
class dip__Options {
using value_type = unsigned long;
value_type values;
constexpr dip__Options( value_type v, int ) : values{ v } {} // used by operator+
public:
constexpr dip__Options() : values( 0 ) {}
explicit constexpr dip__Options( value_type n ) : values{ 1UL << n } {}
constexpr bool operator==( dip__Options const other ) const {
return ( values & other.values ) == other.values;
}
constexpr bool operator!=( dip__Options const other ) const {
return !operator==( other );
}
constexpr dip__Options operator+( dip__Options const other ) const {
return { values | other.values, 0 };
}
dip__Options& operator+=( dip__Options const other ) {
values |= other.values;
return *this;
}
dip__Options& operator-=( dip__Options const other ) {
values &= ~other.values;
return *this;
}
};
} // namespace detail
/// \brief Declare a type used to pass options to a function or class.
/// <snip>
/// For class member values, add `static` in front of `DIP_DEFINE_OPTION`.
#define DIP_DECLARE_OPTIONS( name ) class name##__Tag; using name = dip::detail::dip__Options< name##__Tag >
/// \brief Use in conjunction with `DIP_DECLARE_OPTIONS`. `index` should be no higher than 31.
#define DIP_DEFINE_OPTION( name, option, index ) constexpr name option { index }
} // namespace dip
Some functions using option flags might look like this:
namespace dip {
DIP_DECLARE_OPTIONS( MyOptions );
DIP_DEFINE_OPTION( MyOptions, Option_clean, 0 );
DIP_DEFINE_OPTION( MyOptions, Option_fresh, 1 );
DIP_DEFINE_OPTION( MyOptions, Option_shine, 2 );
void MyFunction( MyOptions opts ) {
if( opts == Option_clean + Option_fresh ) {
std::cout << "Fresh & clean\n";
} else {
if( opts == Option_clean )
std::cout << "It's clean!\n";
if( opts == Option_fresh )
std::cout << "It's fresh!\n";
if( opts == Option_shine )
std::cout << "Oh, shiny\n";
}
}
DIP_DECLARE_OPTIONS( YourOptions );
DIP_DEFINE_OPTION( YourOptions, Option_red, 0 );
DIP_DEFINE_OPTION( YourOptions, Option_green, 1 );
void YourFunction( YourOptions opts ) {
if( opts == Option_red )
std::cout << "We've got red\n";
if( opts == Option_green )
std::cout << "We've got green\n";
}
} // namespace dip
And then using these functions would look like this:
int main() {
dip::MyFunction( dip::Option_shine );
dip::MyFunction( dip::Option_clean + dip::Option_fresh );
dip::YourFunction( dip::Option_red + dip::Option_green );
//dip::YourFunction( dip::Option_shine ); // compile error
//auto opts = dip::Option_clean + dip::Option_red; // compile error
}