1

The typical C-style approach:

#define LOG_ERRORS            1  // 2^0, bit 0
#define LOG_WARNINGS          2  // 2^1, bit 1
#define LOG_NOTICES           4  // 2^2, bit 2
#define LOG_INCOMING          8  // 2^3, bit 3
#define LOG_OUTGOING         16  // 2^4, bit 4
#define LOG_LOOPBACK         32  // and so on...

// Only 6 flags/bits used, so a char is fine
unsigned char flags;

// initialising the flags
flags = LOG_ERRORS;

//initialising to multiple values with OR (|)
flags = LOG_ERRORS | LOG_WARNINGS | LOG_INCOMING;
// sets to 1 + 2 + 8 i.e. bits 0, 1 and 3

// testing for a flag
// AND with the bitmask before testing with ==
if ((flags & LOG_WARNINGS) == LOG_WARNINGS)
   ...

// testing for multiple flags
// as above, OR the bitmasks
if ((flags & (LOG_INCOMING | LOG_OUTGOING))
         == (LOG_INCOMING | LOG_OUTGOING))
   ...

Is there a better way in C++11, which keeps the old C style interface (LOG_INCOMING | LOG_OUTGOING)? i.e. How can I get rid of the "ugly" way to check which bits are set?

I have been looking at std::bitset but this latter only tests for positional queries (i.e. for example it can test if 3rd bit is set) and cannot test for something like this:

  LOG_INCOMING | LOG_OUTGOING
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Kam
  • 5,878
  • 10
  • 53
  • 97
  • The equals is not required in `if ((flags & LOG_WARNINGS) == LOG_WARNINGS)`. `if (flags & LOG_WARNINGS)` is sufficient. – engineerC Oct 31 '14 at 00:11
  • A bit is still true and false. Use constants with a type if you want to make it c++y. – Captain Giraffe Oct 31 '14 at 00:13
  • Possible duplicate of [How do you set, clear and toggle a single bit in C/C++?](http://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit-in-c-c/88934#88934) – πάντα ῥεῖ Oct 31 '14 at 00:13
  • 2
    What does "C++11eny" mean? And why is your question tagged [tag:c]? – Lightness Races in Orbit Oct 31 '14 at 00:13
  • http://www.drdobbs.com/the-standard-librarian-bitsets-and-bit-v/184401382 – engineerC Oct 31 '14 at 00:15
  • C++11ny means C++11ny :) i.e. using new features of the language to keep type safety, cleanness etc... My question is tagged C because the code I used to explain my question is in C – Kam Oct 31 '14 at 00:15
  • Well, you should explain your question in english, maybe with code in the questions programming-language. Just because you give an *additional* example in a second language is no excuse to tag with that (There are few exceptions, when asking about the equivalent to a specific language-feature). Also, the code you posted *is C++*. – Deduplicator Oct 31 '14 at 00:18
  • Why is my question down voted jeez. Please explain? It is clear, concise, describes an issue, can be helpful for future SO users... – Kam Oct 31 '14 at 00:20
  • @Kam: I agree; an upvote from me. – Lightness Races in Orbit Oct 31 '14 at 00:23
  • @Kam: Is my change good, or did I misinterpret you? – Deduplicator Oct 31 '14 at 00:25
  • @Deduplicator, I might have overriden your changed by mistake. Can you please re-edit the post to see what you have done? – Kam Oct 31 '14 at 00:28
  • I can't wrap my head around why it's written as `if( (flags & LOG_WARNINGS) == LOG_WARNINGS )` instead of just `if( flags & LOG_WARNINGS )`. Are there any differences I'm not aware of? – dyp Oct 31 '14 at 00:30
  • @dyp, DO NOT use the equality operator (i.e. bitflags == bitmask) for testing if a flag is set - that expression will only be true if that flag is set and all others are unset. To test for a single flag you need to use & and == http://www.dylanleigh.net/notes/c-cpp-tricks.html – Kam Oct 31 '14 at 00:31
  • 1
    @Kam I'm not suggesting `if( flags == LOG_WARNINGS )`, I'm suggesting `if( flags & LOG_WARNINGS )`. As far as I can tell, this erases all other bits and tests if the result is nonzero, which should have the same effect as the code you've posted. – dyp Oct 31 '14 at 00:52
  • @Kam: The `==` part is redundant, even in C. Non-zero is `true` in conditionals since before `bool` was invented. I don't understand where this myth that you have to write `if ((x & y) == y)` instead of just `if (x & y)` originated. – Lightness Races in Orbit Oct 31 '14 at 13:56

3 Answers3

4

I would replace your macros with static const ints (well, or an enum with explicit values), but other than that your implementation is just fine.

You should not replace good, solid, robust, clear, self-documenting, concise, reliable code with some new template monstrosity just because you can.

Your code is modern enough and this pattern is still very much in use.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    @Deduplicator: Three more C++11 answers for my gold badge. Can't wait! – Lightness Races in Orbit Oct 31 '14 at 00:16
  • So you don't think improving `(flags & LOG_WARNINGS) == LOG_WARNINGS` is worth it? – dyp Oct 31 '14 at 00:16
  • 1
    @dyp: Well, I'd call it `(flags & LOG_WARNINGS)` and possibly hide it behind an inline function for additional clarity, but really the use of bitsets is so ingrained into our craft that I wouldn't go messing around with it unless there were a problem. – Lightness Races in Orbit Oct 31 '14 at 00:17
  • The problem I have with this statement is that the same could be said about C-style arrays and raw owning pointers (but to a much higher degree). We similarly know about the issues of scoping and type safety that static consts and unscoped enums impose on us; so IMHO there's a consideration to be made *if* the code could become clearer and if so, how much we want to deviate from our old paths to achieve that. – dyp Oct 31 '14 at 00:23
  • @dyp: Right, but what I'm saying is that I don't think it can, really. And there's a _lot_ less to gain than with C-style arrays and raw owning pointers. A _lot_. – Lightness Races in Orbit Oct 31 '14 at 00:24
3

I don't see anything wrong with the performance of the code that 0x/1y features will help. If it's already well tested, you probably want to avoid a re-write (especially if existing code depends on it).

If just want some ideas for how you could use features though, there are some different approaches you could take.

constexpr...

constexpr uint8_t bit(const uint8_t n) {
  return 1 << n;
}

constexpr static const uint8_t LOG_ERRORS =   bit(0);
constexpr static const uint8_t LOG_WARNINGS = bit(1);

if (flags & (LOG_ERROR | LOG_WARNINGS))

binary literals...

static const uint8_t LOG_ERRORS =   0b00000001;
static const uint8_t LOG_WARNINGS = 0b00000010;

if (flags & (LOG_ERRORS | LOG_WARNINGS))

variadic templates...

template<typename T, typename... Ts>
T bit_or(T t, Ts... ts) {
  return t | bit_or(ts...);
}

template<typename T>
T bit_or(T t) {
  return t;
}

template<typename T, typename... Ts>
bool any_set(T t, Ts... ts) {
  return static_cast<bool>(t & (bit_or(ts...)));
}

constexpr uint8_t bit(const uint8_t n) {
  return 1 << n;
}

constexpr static const uint8_t LOG_ERRORS   = bit(0);
constexpr static const uint8_t LOG_WARNINGS = bit(1);

if (any_set(flags, LOG_ERRORS, LOG_WARNINGS))

My personal preference would be to avoid std::bitset, since it can't be directly evaluated in a boolean context. However, I might consider wrapping flags in a class, and using an enum class : uint8_t for the flags for type safety. The class would probably be something similar to Java's EnumSet. You could easily overload the bitwise operations (&, |, ^, ~, etc...) for it to preserve the C interface.

Jason
  • 3,777
  • 14
  • 27
1

There is bitset, which, among other things, allows you to set an N bit to true; it also has some methods for conversions to unsigned and to string ( C++ string, not just a null terminated sequence of char C-style )

Other than that, I don't think that there is a more C++-ish way of doing this, but I'll probably keep an approach similar to what Lightness Races in Orbit just described, be conservative and don't add an overhead that you don't need .

user2485710
  • 9,451
  • 13
  • 58
  • 102