|
| 1 | + |
| 2 | +== `atomic_bitset.hpp` |
| 3 | + |
| 4 | +https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/atomic_bitset.hpp[`atomic_bitset.hpp`] |
| 5 | +provides an implementation of a xref:bitset.adoc#_bitset_hpp[`bitset`] with atomic semantics. |
| 6 | + |
| 7 | +An `atomic_bitset` is limited in size to the maximum integral type a platform |
| 8 | +can support while still using lock-free atomic instructions. Like `bitset`, it |
| 9 | +can be defined by selecting the underlying storage type automatically: |
| 10 | +[source,cpp] |
| 11 | +---- |
| 12 | +using A = stdx::bitset<8>; // uses uint8_t |
| 13 | +using B = stdx::bitset<16>; // uses uint16_t |
| 14 | +using C = stdx::bitset<32>; // uses uint32_t |
| 15 | +using D = stdx::bitset<64>; // uses uint64_t |
| 16 | +---- |
| 17 | + |
| 18 | +`atomic_bitset` is constructed in the same way as `bitset`: with `all_bits`, |
| 19 | +`place_bits`, a value, or a `string_view`: |
| 20 | +[source,cpp] |
| 21 | +---- |
| 22 | +using namespace std::string_view_literals; |
| 23 | +auto bs0 = stdx::atomic_bitset<8>{}; |
| 24 | +auto bs1 = stdx::atomic_bitset<8>{stdx::all_bits}; // 0b1111'1111 |
| 25 | +auto bs2 = stdx::atomic_bitset<8>{stdx::place_bits, 0, 1, 3}; // 0b1011 |
| 26 | +auto bs3 = stdx::atomic_bitset<8>{0b1011}; |
| 27 | +auto bs4 = stdx::atomic_bitset<8>{"1011"sv}; |
| 28 | +---- |
| 29 | + |
| 30 | +NOTE: `atomic_bitset`'s constructors are `constexpr`, but none of the other |
| 31 | +functions are. |
| 32 | + |
| 33 | +Also like `bitset`, `atomic_bitset` supports conversion to integral types: |
| 34 | +[source,cpp] |
| 35 | +---- |
| 36 | +auto bs = stdx::atomic_bitset<11>{0b101}; // 11 bits, value 5 |
| 37 | +auto i = bs.to<std::uint64_t>(); // 5 (a std::uint64_t) |
| 38 | +auto j = bs.to_natural(); // 5 (a std::uint16_t) |
| 39 | +---- |
| 40 | + |
| 41 | +And operation with enumeration types: |
| 42 | +[source,cpp] |
| 43 | +---- |
| 44 | +enum struct Bits { ZERO, ONE, TWO, THREE, MAX }; |
| 45 | +auto bs = stdx::atomic_bitset<Bits::MAX>{stdx::all_bits}; // 4 bits, value 0b1111 |
| 46 | +bs.set(Bits::ZERO); |
| 47 | +bs.reset(Bits::ZERO); |
| 48 | +bs.flip(Bits::ZERO); |
| 49 | +auto bit_zero = bs[Bits::ZERO]; |
| 50 | +---- |
| 51 | + |
| 52 | +Unlike `bitset`, `atomic_bitset`'s operations are atomic. For example, `load` |
| 53 | +and `store` are basic operations that return and take a corresponding `bitset`: |
| 54 | + |
| 55 | +[source,cpp] |
| 56 | +---- |
| 57 | +constexpr auto bs = stdx::atomic_bitset<8>{0b1010ul}; |
| 58 | +auto copy = bs.load(); // a stdx::bitset<8>{0b1010ul}; |
| 59 | +bs.store(copy); |
| 60 | +---- |
| 61 | + |
| 62 | +Like https://en.cppreference.com/w/cpp/atomic/atomic/load[`load`] and |
| 63 | +https://en.cppreference.com/w/cpp/atomic/atomic/store[`store`] on |
| 64 | +https://en.cppreference.com/w/cpp/atomic/atomic[`std::atomic`], the `load` and |
| 65 | +`store` operations on `stdx::atomic_bitset` take an optional |
| 66 | +https://en.cppreference.com/w/cpp/atomic/memory_order[`std::memory_order`]. |
| 67 | +`stdx::atomic_bitset` is also implicitly convertible to a corresponding |
| 68 | +`stdx::bitset`; that operation is equivalent to `load()`. |
| 69 | + |
| 70 | +The `set`, `reset` and `flip` operations also take an optional |
| 71 | +`std::memory_order`: these operations are equivalent to `store` in their |
| 72 | +semantics, except that they return the `stdx::bitset` that was the previous |
| 73 | +value. |
| 74 | + |
| 75 | +[source,cpp] |
| 76 | +---- |
| 77 | +constexpr auto bs = stdx::atomic_bitset<8>{0b1010ul}; |
| 78 | +auto prev = bs.set(0); |
| 79 | +// bs == 1011 |
| 80 | +// prev == 1010 (stdx::bitset<8>) |
| 81 | +---- |
| 82 | + |
| 83 | +NOTE: When `set` or `reset` are called without specifying bits, they return a |
| 84 | +reference to the `atomic_bitset`. This is because these operations result in a |
| 85 | +plain `store` which does not return the previous value. |
| 86 | + |
| 87 | +`all`, `any`, `none` and `count` are also available on `atomic_bitset` and they |
| 88 | +are each equivalent to `load` followed by the respective operation. Like `load`, |
| 89 | +they also take an optional `std::memory_order`. |
| 90 | + |
| 91 | +So what is _not_ available on `atomic_bitset`? |
| 92 | + |
| 93 | + * any binary operation: equality, binary versions of `and`, `or`, etc. |
| 94 | + * bit shift operations |
| 95 | + * `for_each` and `lowest_unset` |
| 96 | + * unary `not` |
| 97 | + |
| 98 | +These operations are not provided for varying reasons: |
| 99 | + |
| 100 | + * atomic semantics are impossible or problematic to guarantee (binary operations) |
| 101 | + * atomic instructions are not available (bit shifts, `lowest_unset`) |
| 102 | + * atomic semantics are unclear (`for_each`) |
| 103 | + * the caller can easily achieve what they want (unary `not`) |
| 104 | + |
| 105 | +In all of these cases though, the caller can make the right choice for them, and |
| 106 | +use the corresponding operations on `bitset` after correctly reasoning about the |
| 107 | +required semantics. |
0 commit comments