Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

basicpp/flags17

Repository files navigation

C++17 Flags

Summary of flags with C++17.

For the new experimental compiler of Rebuild language project I needed all kinds of flags. This ended up being a rabbit hole. Here is what I digged out of that.

Legacy enum approach

Basically taken from C to C++.

enum Animals {
 ANIMALS_CAT = 1 << 0,
 ANIMALS_DOG = 1 << 1,
 ANIMALS_WOLF = 1 << 3,
};
// define operator to prevent fallback to int
auto operator|(Animals a, Animals b) -> Animals { return Animals(int(a) | b); }
auto operator&(Animals a, Animals b) -> Animals { return Animals(int(a) & b); }
// define stream operator for bitmask
auto operator<<(std::ostream &out, Animals a) -> std::ostream & {
 // ...see main.cpp for details
}
void usage() {
 auto a = ANIMALS_CAT | ANIMALS_DOG;
 std::cout << a << '\n';
 std::cout << ((a & ANIMALS_CAT) | ANIMALS_WOLF) << '\n';
}

References:

  • C-APIs: Posix, ffmpeg, win32 ...

Advantages:

  • very well known pattern
  • syntax compatible with C

Disadvantages:

  • enum value names leak to namespace
  • programmer has to ensure that all bits are unique
  • hard to spot skipped bits
  • no iteration on bits possible
  • no type safety (easy to mix different masks)

Classical enum class approach

The entries inside the enum class are the bit mask for each flag.

enum class Animal {
 Cat = 1 << 0,
 Dog = 1 << 1,
 Wolf = 1 << 3,
};
using Animals = classic::Flags<Animal>;
ENABLE_CLASSIC_FLAGS_OP(Animals)
// stream operator for a single value
auto operator<<(std::ostream &out, Animal a) -> std::ostream & {
 // ...see main.cpp for details
}
void usage() {
 auto a = Animal::Cat | Animal::Dog;
 std::cout << a << '\n';
 std::cout << ((a & Animal::Cat) | Animal::Wolf) << '\n';
}

References:

  • Qt framework: QFlags<T>
  • most of the flags helpers out there

Advantages:

  • many C and C++ programmers are familiar with this approach.
  • programmer sees everything
  • programmer controls underlying data type
  • allows skipped bits

Disadvantages:

  • programmer has to ensure that all bits are unique
  • hard to spot skipped bits
  • no iteration over unset bits possible

Restrictions:

  • can be implemented in any C++ version
  • benefits from C++11 enum class but requires macro or type trait to allow operator | for enum values

Bit number based approach

New modification of the classical enum class approach.
The entries contain the bit number of the flag.

enum class Animal { Cat, Dog, Wolf = 3 };
using Animals = bitnumber::Flags<Animal>;
ENABLE_BITNUMBER_FLAGS_OP(Animals)
// stream operator for a single value
auto operator<<(std::ostream &out, Animal a) -> std::ostream & {
 // ...see main.cpp for details
}
void usage() {
 auto a = Animal::Cat | Animal::Dog;
 std::cout << a << '\n';
 std::cout << ((a & Animal::Cat) | Animal::Wolf) << '\n';
}

References:

  • new (I could not find an existing implementation)

Advantages:

  • automatic enum values work
  • programmer still has control
  • allows skipped bits

Disadvantages:

  • programmer has to ensure all bits are unique
  • no iteration over unset bits possible
  • flag storage data type is disconnected
  • no way to ensure all flags fit

Restrictions:

  • same as classical approach

Tag type based approach

All flags are represented by tag types.

Hint: A tag type is a type we only use to get a unique name.

struct Animal {
 struct Cat;
 struct Dog;
 struct Wolf;
};
using Animals = tagtype::Flags<Animal::Cat, Animal::Dog, void, Animal::Wolf>;
auto operator<<(std::ostream &out, tagtype::Flag<Animal::Cat>) -> std::ostream & {
 return out << "Cat";
} // ... repeated for other types
void usage() {
 auto a = Animals(Flag<Animal::Cat>, Flag<Animal::Dog>);
 std::cout << a << '\n';
 std::cout << (a & Flag<Animal::Cat> | Flag<Animal::Wolf>) << '\n';
}

References:

Advantages:

  • full encapsulation
  • flags manages bit layout
  • flag type knows all possible flags
  • can iterate over all possible flags
  • types are quite flexible

Disadvantages:

  • tags have to be repeated
  • less control over bit layout
  • hard to deprecate flags or skip bits
  • tag types are no values

Restrictions:

Tag value based approach

Each flag is represented by a tag value.

enum class Animal { Cat, Dog };
enum class AnimalWolf {}; // any type allowed
using Animals = tagvalue::Flags<Animal::Cat, Animal::Dog, nullptr, AnimalWolf{}>;
using tagvalue::operator<<;
auto operator<<(std::ostream &out, tagvalue::Typed<Animal> a) -> std::ostream & {
 // ... see main.cpp
}
auto operator<<(std::ostream &out, tagvalue::Typed<AnimalWolf>) -> std::ostream & {
 return out << "Wolf";
}
void usage() {
 auto a = Animals(Flag<Animal::Cat>, Flag<Animal::Dog>);
 std::cout << a << '\n';
 std::cout << (a & Flag<Animal::Cat> | Flag<AnimalWolf{}>) << '\n';
}

References:

  • new (I could not find any usages of this approach)

Advantages:

  • full encapsulation
  • no restrictions to the values (they are just tags)
  • can iterate over all possible flags

Disadvantages:

  • tags have to be repeated
  • less control over bit layout

Restrictions:

  • requires the C++17 template<auto V> feature

Repeated bit value approach

The underlying values of the tags are used as bit number.

enum class Animal { Cat, Dog, Wolf = 3 };
using Animals = repeated::Flags<Animal::Cat, Animal::Dog, Animal::Wolf>
ENABLE_REPEATED_FLAGS_OP(Animals)
auto operator<<(std::ostream &out, Animal a) -> std::ostream & {
 // ... see main.cpp
}
void usage() {
 auto a = Animal::Cat | Animal::Dog;
 std::cout << a << '\n';
 std::cout << ((a & Animal::Cat) | Animal::Wolf) << '\n';
}

References:

  • new (I could not find any usages of this approach)

Advantages:

  • full encapsulation
  • underlying type will always fit
  • duplicate bit values lead to compile error
  • can iterate over all possible flags

Disadvantages:

  • enum values have to be repeated

Restrictions:

  • requires the C++17 template<auto V> feature
    • C++14 requires to repeat the enum type as well. repeated<Animal, Animal::Cat, ...>

Summary

There is no perfect solution in C++. C++17 makes some new variants more viable.

Contribute

Feel free to add PR for improvements or new flag implementations.

License

Apache License

see LICENSE file for details.

About

A comparison of different options to implement binary flags in C++17

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

AltStyle によって変換されたページ (->オリジナル) /