All Projects → basicpp17 → flags17

basicpp17 / flags17

Licence: Apache-2.0 license
A comparison of different options to implement binary flags in C++17

Programming Languages

C++
36643 projects - #6 most used programming language
QML
638 projects

Projects that are alternatives of or similar to flags17

opzioni
The wanna-be-simplest command line arguments library for C++
Stars: ✭ 29 (+81.25%)
Mutual labels:  constexpr
Compile Time Regular Expressions
A Compile time PCRE (almost) compatible regular expression matcher.
Stars: ✭ 2,144 (+13300%)
Mutual labels:  constexpr
fixed string
C++17 string with fixed size
Stars: ✭ 64 (+300%)
Mutual labels:  constexpr
uninttp
A universal type for non-type template parameters for C++20 or later.
Stars: ✭ 16 (+0%)
Mutual labels:  constexpr
bitset2
std::bitset with constexpr implementations plus additional features.
Stars: ✭ 76 (+375%)
Mutual labels:  constexpr
literal ipaddr
C++17 constexpr implementation of inet_addr / inet_aton / inet_pton
Stars: ✭ 19 (+18.75%)
Mutual labels:  constexpr
constexpr-java
build-time code-execution for java, a bit like constexpr in C++11
Stars: ✭ 58 (+262.5%)
Mutual labels:  constexpr

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.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].