Skip to main content
GameDev.net gamedev.net
🔒 Locked

C++ Operator Overloading: Bitwise Operators & If-Check

Started by Flyverse Aug 2, 2015 at 1:55 PM 15 replies 7.7k views
Original Post
Flyverse
Flyverse

Hey guys!

Since I need to use flags, but don't want to restrict myself to a maximum number of flags (Depending on the type I use: char, int, etc), I thought about creating some kind of templated class storing T's in a list, on which operators can be used to make it look like standard C++ Bitwise flags.

(First things first: I don't know much about all that bitwise stuff)

Let's name my class "UFlag". How I want to be able to use it:


//Long way
UFlag<int> flag;
flag | 10 | 55;
if(flag & 10);
    //do stuff. This will be true, obviously.

//But also on the fast way:
myMethodTakingUFLAGasArgument(UFlag<int> | 10 | 55) //Somehow like this, so that I don't have to declare my variable first...

...

void myMethodTakingUFLAGasArgument(UFlag<int> f){
    UFlag<int> uf | 10; //I want also to be able to declare it like this... Saves one line.
    if(f & uf) ;//do stuff. should be true with the example-argument above.
    if(f & 55) ;//do stuff. should be true with the example-argument above.
}

Here is what I have right now: https://ideone.com/7jtnep

Unfortunately, not everything is working yet.

1 - I have not figured out yet how to apply the |/& operators in the same line as where I create the object.

2 - if checking doesnt work yet, as UFlag isnt a boolean.

How do I solve these two problems?

Kind regards,

Flyverse

Edit: Solved Problem#2.

AzureBlaze
AzureBlaze

1. Normally the only operator that will return a reference to itself is an assignment operator like = or +=. The rest should take constant parameters (including "this") and return a new object. "c = a + b" does not alter the content of a or b.

UFlag operator |(const UFlag &b) const

should do the trick.

2. Implement explicit operator bool() const;

BitMaster
BitMaster
You might want to look at std::bitset. Either because it does everything you want already or as an inspiration.
AxeGuywithanAxe
AxeGuywithanAxe

operator bool ()const

{

... your boolean logic

}

it's the same way you can cast anything to another, i.e



struct Mask
{
  Mask(int v): 
  value(v)
  {
  }
  operator int()const{ return value;}

private:

int value;
};

 void SomeFunction(int i)
{

}

int main()
{
  Mask MyMask(5);

  SomeFunction(MyMask);

}
SeanMiddleditch
SeanMiddleditch
First, let's address some problems:

You can't do this, C/C++ grammar doesn't work this way at all, and not even horrible hacks can make it work:
UFlag<int> uf | 10; //I want also to be able to declare it like this... Saves one line.
Try:
UFlag<int> uf{10};
Second, your conditional idiom is slightly problematic even with plain integers, unless you particularly like getting lots of warnings:
    if(f & uf) ;//do stuff. should be true with the example-argument above.
    if(f & 55) ;//do stuff. should be true with the example-argument above.
Prefer:
if((f & uf) != uf)
Alright, on to the main event.

I don't like using complex wrapper types for flags. We have enums. Let's use them. On some calling conventions, you'll actually be penalized for using classes/structs where you could have used a primitive type (even small structs may always be passed via the stack and never registers), so it's doubly important that we stick to plain enums if they'll ever be used anywhere perf-sensitive in multi-platform code.

C++11 strong enums have further advantages (you can specify the size explicitly, and they're namespaced). However, since you can't use operators like | and & with C++11 strong enums, but we want them for flags, but we _only_ want them enabled for flags, we need a new type trait.

You could use a wrapper type and just adapt the type traits I have here to simply checking against your wrapper type if you prefer the wrapper or just dislike type traits.

Here's my "flagset.h" (minus some helper functions that I rarely use) that I made for my personal toys/experiments:

// Sean Middleditch <sean@middleditch.us> - 2014
// This is free and unencumbered software released into the public domain.
// 
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
// 
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// 
// For more information, please refer to <http://unlicense.org/>

#if !defined(_guard_STLX_FLAGSET_H)
#define(_guard_STLX_FLAGSET_H)
#pragma once

#include <type_traits>

namespace stlx
{
	template <typename T> struct is_flagset;
	template <typename T> constexpr T bitmask(T);
}

/// Specialize to indicate that an enum is a flag set (and should be combinable).
/// @tparam T the enumeration to mark.
template <typename T> struct stlx::is_flagset : std::false_type {};

/// Set a given bit 1, all other bits to 0.
/// @param bit the bit to set.
template <typename T>
constexpr T stlx::bitmask(T bit) { return T(1) << bit; }

// Operators for flagsets.
template <typename E, typename = std::enable_if_t<stlx::is_flagset<E>::value>>
constexpr E operator|(E lhs, E rhs) { return E(std::underlying_type_t<E>(lhs) | std::underlying_type_t<E>(rhs)); }

template <typename E, typename = std::enable_if_t<stlx::is_flagset<E>::value>>
constexpr E operator&(E lhs, E rhs) { return E(std::underlying_type_t<E>(lhs) & std::underlying_type_t<E>(rhs)); }

template <typename E, typename = std::enable_if_t<stlx::is_flagset<E>::value>>
constexpr E operator^(E lhs, E rhs) { return E(std::underlying_type_t<E>(lhs) ^ std::underlying_type_t<E>(rhs)); }

template <typename E, typename = std::enable_if_t<stlx::is_flagset<E>::value>>
constexpr E& operator|=(E& lhs, E rhs) { return lhs = E(std::underlying_type_t<E>(lhs) | std::underlying_type_t<E>(rhs)); }

template <typename E, typename = std::enable_if_t<stlx::is_flagset<E>::value>>
constexpr E& operator&=(E& lhs, E rhs) { return lhs = E(std::underlying_type_t<E>(lhs) & std::underlying_type_t<E>(rhs)); }

template <typename E, typename = std::enable_if_t<stlx::is_flagset<E>::value>>
constexpr E& operator^=(E& lhs, E rhs) { return lhs = E(std::underlying_type_t<E>(lhs) ^ std::underlying_type_t<E>(rhs)); }

template <typename E, typename = std::enable_if_t<stlx::is_flagset<E>::value>>
constexpr E operator~(E rhs) { return E(~std::underlying_type_t<E>(rhs)); }

#endif // defined(_guard_STLX_FLAGSET_H)
And some example usage modeled from a virtual file system wrapper I have:

enum class EFileMode : std::uint16_t
{
	None = 0,
	Read = stlx::bitmask(0U),
	Write = stlx::bitmask(1U),
	Create = stlx::bitmask(2U),
	Exclusive = stlx::bitmask(3U),
	Truncate = stlx::bitmask(4U),
	MakeDir = stlx::bitmask(5U),

	WriteNew = Write|Create|Truncate|MakeDir,
};
namespace stlx
{
	template<> struct is_flagset<EFileMode> : std::true_type {};
}

File OpenFile(string_view path, EFileMode mode) {
  // silly example
  if ((mode & (EFileMode::Truncate | EFileMode::Create)) != EFileMode::None);
    mode &= EFileMode::Write;
  ...
  return file;
}

auto file = OpenFile(path, EFileMode::Write | EFileMode::Create | EFileMode::Truncate);
Sean Middleditch – Game Systems Engineer – Join my team!
SeanMiddleditch
SeanMiddleditch
Oh yeah, another advantage of using enums is that good IDEs (like Visual Studio) will properly visualize them in the debugger view.

They even innately visualize flags of enum values. So
EFileMode mode = EFileMode::Create | EFileMode::Truncate
will actually show up in the watch window as
mode  Create|Truncate
You can't get your wrapper type to do that properly.
Sean Middleditch – Game Systems Engineer – Join my team!
Servant of the Lord
Servant of the Lord

Second, your conditional idiom is slightly problematic even with plain integers, unless you particularly like getting lots of warnings:


if(f & uf) ;//do stuff. should be true with the example-argument above.
Prefer:

if((f & uf) != uf)

Why is the second preferred?


unsigned f  = (1 | 2 | 4 | 8);
unsigned uf = 4;

(f & uf) = uf
uf is non-zero
if(non-zero) = true
Trienco
Trienco

Second, your conditional idiom is slightly problematic even with plain integers, unless you particularly like getting lots of warnings:


if(f & uf) ;//do stuff. should be true with the example-argument above.
Prefer:

if((f & uf) != uf)

Why is the second preferred?

I suspect it was supposed to be (f & uf) != 0, since the other version is definitely not equivalent. It prevents the implicit cast from unsigned to bool and all the potential compiler warnings that come with it (in VS it's usually something about runtime performance).

f@dzhttp://festini.device-zero.de
Washu
Washu

I suspect it was supposed to be (f & uf) != 0, since the other version is definitely not equivalent. It prevents the implicit cast from unsigned to bool and all the potential compiler warnings that come with it (in VS it's usually something about runtime performance).

I do not know what his intention was, however typically if you're testing flags and you want to ensure that all of the flags you are testing are set, you want (flags & flagsToTestFor) == something, as otherwise only having some of the flags in flagsToTestFor set can still return non-zero.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.
Krohm
Krohm

Maybe I missed the objections but... why do you have to use flags and masks? Unless I have a lower-level component requiring it, I'd stay away from them.

Previously "Krohm"
Flyverse
Flyverse

Thanks for all of your answers.

@AzureBlaze

1. Wouldn't that be quite heavy for performance? Since every single of those instances has it's own list, and is used only shortly anyway..

2. Works, thanks!

@BitMaster

This would do everything I want; If it hadn't a fixed size. I wanted to create such a class exactly because of that: To avoid fixing myself on a fixed size (I mean, I probably won't use more than 4-5 flags anyway at once, so I could just use a 8bit integer, but I like to keep myself free of any restrictions...). Also I don't use boost so I won't be able to use boost::dynamic_bitset. I'll look at it for inspirations though, thanks!

@SeanMiddleditch

In this specific case I can't really allow myself to use enums, since the "flags" that are set are not really what you would expect from flags, but rather IDs generated on-the-fly; I just want to use flags because I really like the syntax (And how easy it is)

Your class looks quite complex, I'll try to understand it later :)

@Servant of the Lord

Exactly :). Although, now that I think of it, if one of the two flag-sets is empty, the intersection between the two should still return the non-empty flag-set... Which kind of defies the logic behind it. I'll explain what I actually want to do and why I want to use flagset further down this post.

@Krohm Heh, exactly, in reality using flags is probably a really bad idea. I just loved the simplicity that comes with using them, I guess. Here is what I really want to do:

In my EventHandler class, I'm able to trigger an event x, and I can hook some callback method to all events of type y. (Standard event-handler functionality I guess)

Since I want to also create some kind of scripting system later on, in order to define the entities of my game, which are all event-driven: Meaning their whole behaviour consists of callbacks being called from the eventhandler. But of course, I don't really want to let the callbacks be called by all events of type y; Let's take a collision-event as example: If I hook my entity-collision-callback to the collision event, it will be called everytime any collision happens in my game. So I need to apply some constraints, in order to have that event only called when the collision happens with this specific entity and another one.

I thought about applying these constraints with these flags:

When triggering the event, I specify the IDs of both objects colliding. Now, every callback that either has no constraints, or one of those specific constraints, will get called. Here a little pseudo-code, because I'm really bad at explaining stuff (I'll also use my UFlag class, even if it is probably the bad way of doing this.):


#define EIUF ...  //Empty Integer UFlag

//When hooking into the event. Inside my entity-class
eventHandler->hookInto(EventType::Collision, myCollisionCallback, UFlag<int>(this->ID());

//When triggering the event:

eventHandler->triggerEvent(EventType::Collision, someArgument, UFlag<int>(collision.firstObject.ID(), collision.secondObject.ID()));

//If my entity has the same ID as one of the two in the collision event, it's callback will be called.

//If a callback is specified without constraints, it will get called, regardless of the IDs.


Depending on the event, the integer-flags could mean something else.

I hope I could make myself clear unsure.png ...

Does anyone have an idea what I should use instead of those flags? As I already said, the thing is, I reaaally liked the syntax and everything.

Kind regards

Servant of the Lord
Servant of the Lord

I suspect it was supposed to be (f & uf) != 0, since the other version is definitely not equivalent. It prevents the implicit cast from unsigned to bool and all the potential compiler warnings that come with it (in VS it's usually something about runtime performance).

I do not know what his intention was, however typically if you're testing flags and you want to ensure that all of the flags you are testing are set, you want (flags & flagsToTestFor) == something, as otherwise only having some of the flags in flagsToTestFor set can still return non-zero.

So then, is the recommended method:


if((flags & flagsToTestFor) == flagsToTestFor) //The flag entirely exists (all the matching bits are 1).
if((flags & flagsToTestFor) != flagsToTestFor) //The flag doesn't entirely exist (not every matching bits is 1).

?

Flyverse
Flyverse

@Servant of the Lord

Not really, I think. I did it like that:

if (!flagsToTestFor || flagsToTestFor & flags)

Given that the "&" operator returns the non-empty flag-set if one of the two is empty (See the ideone-code in the first post).

(Also, not all flags must match: If one flag matches, it is enough to be "true" since the boolean-operator-override-thingy returns the size of the internal list: So it is only false if it is empty.)

But honestly, right now I think that I really shouldn't use flags anyway, but I can't imagine another way which is as easy...

Edit: oh whoops, I didn't notice that you weren't responding to me, sorry

BitMaster
BitMaster

@BitMaster
This would do everything I want; If it hadn't a fixed size. I wanted to create such a class exactly because of that: To avoid fixing myself on a fixed size (I mean, I probably won't use more than 4-5 flags anyway at once, so I could just use a 8bit integer, but I like to keep myself free of any restrictions...). Also I don't use boost so I won't be able to use boost::dynamic_bitset. I'll look at it for inspirations though, thanks!


I'm not sure I understand. A single number in a typedef (or using declaration) needs to be changed whenever you need more bits, everything else happens automatically thanks to template magic. That is in practically all cases a compile-time decision.

In the extreme case that you don't know about your flags until runtime (which you did not really say and is not really obvious from your examples), then yes, it won't work.
Flyverse
Flyverse

@BitMaster

Technically, I could be able to know how many bits are needed in compile-time; But since I want to implement a scripting system later on, in which those flags will be used too, I don't want to restrict the number of bits needed. Also, right now I think that bit-flags are the wrong approach to my problem anyway (See my posts above). Thanks for your help though, it is greatly appreciated!

Kind regards,

Flyverse

Flyverse
Flyverse

Solved my problem, but it was awesome to learn a bit about bits and flags. Thank you guys!

AzureBlaze
AzureBlaze

1. Wouldn't that be quite heavy for performance? Since every single of those instances has it's own list, and is used only shortly anyway..

If you do care about performance, you should revert to normal flags. Your linear search through the deque will already be too slow if you check a billion time through a billion flags.

But I guess this is not the case you're going to use it with. For these non performance critical part you should always choose the easier way. Easier to code, understand, maintain, less chance of misusing. Those 2 microseconds you saved from the user's CPU which you're not paying for is not worth a week of debugging hell.

Your method have severe flaw in it: people don't expect normal operators other than assignments or ++/-- to modify their operand. If you write y = x+5, you'll expect only y will change, not x and certainly not 5.

If you write


UFlag foo = getFlags();
if(foo & 1){
//...
}

if(foo & 2){
//...
}

you are going to run into trouble because the content of foo have changed in the first if statement. You'll have to state clearly in your documents that UFlag can't be used like this and hope people (and yourself) will remember.

Although if you have access to c++11 you can avoid some of the performance penalty by using rvalue reference


//this operator is called when the left hand side is an rvalue, which is some temp value that is going to be thrown away.
//google rvalue for more details.
UFlag &&operator&(const UFlag &rhs) && { 
	//we are not going to use "this" later, so we are free to mess with it to improve performance.
	this->list.blah(...);
	//return as an rvalue
	return move(*this);
}

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.