I'm currently trying to come up with a clever way of implementing flags that include the states "default" and (optional) "toggle" in addition to the usual "true" and "false".
The general problem with flags is, that one has a function and wants to define its behaviour (either "do something" or "don't do something") by passing certain parameters.
With a single (boolean) flag the solution is simple:
void foo(...,bool flag){
if(flag){/*do something*/}
}
Here it is especially easy to add a default, by just changing the function to
void foo(...,bool flag=true)
and call it without the flag parameter.
Once the number of flags increases, the solution i usually see and use is something like this:
typedef int Flag;
static const Flag Flag1 = 1<<0;
static const Flag Flag2 = 1<<1;
static const Flag Flag3 = 1<<2;
void foo(/*other arguments ,*/ Flag f){
if(f & Flag1){/*do whatever Flag1 indicates*/}
/*check other flags*/
}
//call like this:
foo(/*args ,*/ Flag1 | Flag3)
This has the advantage, that you don't need a parameter for each flag, which means the user can set the flags he likes and just forget about the ones he don't care about. Especially you dont get a call like foo (/*args*/, true, false, true)
where you have to count which true/false denotes which flag.
The problem here is:
If you set a default argument, it is overwritten as soon as the user specifies any flag. It is not possible to do somethink like Flag1=true, Flag2=false, Flag3=default
.
Obviously, if we want to have 3 options (true, false, default) we need to pass at least 2 bits per flag. So while it might not be neccessary, i guess it should be easy for any implementation to use the 4th state to indicate a toggle (= !default).
I have 2 approaches to this, but i'm not really happy with both of them:
I tried using something like this up to now:
typedef int Flag;
static const Flag Flag1 = 1<<0;
static const Flag Flag1False= 1<<1;
static const Flag Flag1Toggle = Flag1 | Flag1False;
static const Flag Flag2= 1<<2;
static const Flag Flag2False= 1<<3;
static const Flag Flag2Toggle = Flag2 | Flag2False;
void applyDefault(Flag& f){
//do nothing for flags with default false
//for flags with default true:
f = ( f & Flag1False)? f & ~Flag1 : f | Flag1;
//if the false bit is set, it is either false or toggle, anyway: clear the bit
//if its not set, its either true or default, anyway: set
}
void foo(/*args ,*/ Flag f){
applyDefault(f);
if (f & Flag1) //do whatever Flag1 indicates
}
However what i don't like about this is, that there are two different bits used for each flag. This leads to the different code for "default-true" and "default-false" flags and to the neccessary if instead of some nice bitwise operation in applyDefault()
.
By defining a template-class like this:
struct Flag{
virtual bool apply(bool prev) const =0;
};
template<bool mTrue, bool mFalse>
struct TFlag: public Flag{
inline bool apply(bool prev) const{
return (!prev&&mTrue)||(prev&&!mFalse);
}
};
TFlag<true,false> fTrue;
TFlag<false,true> fFalse;
TFlag<false,false> fDefault;
TFlag<true,true> fToggle;
i was able to condense the apply
into a single bitwise operation, with all but 1 argument known at compile time. So using the TFlag::apply
directly compiles (using gcc) to the same machine code as a return true;
, return false;
, return prev;
or return !prev;
would, which is pretty efficient, but that would mean i have to use template-functions if i want to pass a TFlag
as argument. Inheriting from Flag
and using a const Flag&
as argument adds the overhead of a virtual function call, but saves me from using templates.
However i have no idea how to scale this up to multiple flags...
So the question is: How can i implement multiple flags in a single argument in C++, so that a user can easily set them to "true", "false" or "default" (by not setting the specific flag) or (optional) indicate "whatever is not default"?
Is a class with two ints, using a similar bitwise operation like the template-approach with its own bitwise-operators the way to go? And if so, is there a way to give the compiler the option to do most of the bitwise operations at compile-time?
Edit for clarification:
I don't want to pass the 4 distinct flags "true", "false", "default", "toggle" to a function.
E.g. think of a circle that gets drawn where the flags are used for "draw border", "draw center", "draw fill color", "blurry border", "let the circle hop up and down", "do whatever other fancy stuff you can do with a circle", ....
And for each of those "properties" i want to pass a flag with value either true, false, default or toggle.
So the function might decide to draw the border, fill color and center by default, but none of the rest. A call, roughly like this:
draw_circle (DRAW_BORDER | DONT_DRAW_CENTER | TOGGLE_BLURRY_BORDER) //or
draw_circle (BORDER=true, CENTER=false, BLURRY=toggle)
//or whatever nice syntax you come up with....
should draw the border (specified by flag), not draw the center (specified by flag), blurry the border (the flag says: not the default) and draw the fill color (not specified, but its default).
If i later decide to not draw the center by default anymore but blurry the border by default, the call should draw the border (specified by flag), not draw the center (specified by flag), not blurry the border (now blurrying is default, but we don't want default) and draw the fill color (no flag for it, but its default).
Not exactly pretty, but very simple (building from your Approach 1):
#include <iostream>
using Flag = int;
static const Flag Flag1 = 1<<0;
static const Flag Flag2 = 1<<2;
// add more flags to turn things off, etc.
class Foo
{
bool flag1 = true; // default true
bool flag2 = false; // default false
void applyDefault(Flag& f)
{
if (f & Flag1)
flag1 = true;
if (f & Flag2)
flag2 = true;
// apply off flags
}
public:
void operator()(/*args ,*/ Flag f)
{
applyDefault(f);
if (flag1)
std::cout << "Flag 1 ON\n";
if (flag2)
std::cout << "Flag 2 ON\n";
}
};
void foo(/*args ,*/ Flag flags)
{
Foo f;
f(flags);
}
int main()
{
foo(Flag1); // Flag1 ON
foo(Flag2); // Flag1 ON\nFlag2 ON
foo(Flag1 | Flag2); // Flag1 ON\nFlag2 ON
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With