Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/design/bit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# bit

Source code: https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/bit.hpp
Documentation: https://intel.github.io/cpp-std-extensions/#_bit_hpp

## No more manual bit twiddling!

A lot of the functionality in this header exists to put an end to manual bit
shifting, masking, etc. Yes it's easy, but it's not simple. Manual bit
operations are prone to sign and integer promotion errors, and sprinkling
`static_cast` everywhere to guard against this makes things ugly and less
understandable.

## `to_be`, `from_be`, `to_le`, `from_le`

`to_be` and `from_be` have identical implementations. As do `to_le` and
`from_le`. But they are different in showing the intent of user code. And also
different in pre and post conditions:

```cpp
// pre: x is big-endian
// post: x is platform-endian
auto x = stdx::from_be(y);

// pre: x is platform-endian
// post: x is big-endian
auto x = stdx::to_be(y);
```

In other words, the use of `from_be`/`to_be` is very different from the use of
`std::byteswap` in cross-platform friendly code.

## `bit_pack` and `bit_unpack`

These functions have some interesting endianness concerns. It is important that
the functions round-trip properly.

```cpp
std::uint8_t a{0x12u}, b{0x34u}, c{0x56u}, d{0x78u};
auto x = stdx::bit_pack<std::uint32_t>(a, b, c, d);
auto [a_, b_, c_, d_] = stdx::bit_unpack<std::uint8_t>(x);
// a == a_, b == b_; c == c_, d == d_
```

This also means that that "written order" (big-endian) is the expected ordering.

```cpp
auto [a, b, c, d] = stdx::bit_unpack<std::uint8_t>(0x12345678u);
// a == 0x12, b == 0x34; c == 0x56, d == 0x78
```

## `bit_destructure`

`bit_destructure` and `bit_unpack` are similar, but where `bit_unpack` is
"big-endian" (for the above reasons), `bit_destructure` is "little-endian".
Because it makes sense to count **up** in bits.

Also, N split points mean N+1 values.

```cpp
auto [a, b, c, d] = stdx::bit_destructure<8, 16, 24>(0x12345678u);
// a == 0x78, b == 0x56; c == 0x34, d == 0x12
```
29 changes: 29 additions & 0 deletions docs/design/cached_latched.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# cached and latched

Source code: https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/latched.hpp
Documentation: https://intel.github.io/cpp-std-extensions/#_latched_hpp

Source code: https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/cached.hpp
Documentation: https://intel.github.io/cpp-std-extensions/#_latched_hpp

## Lazy values

Both `cached` and `latched` model lazy computation of values. Under the hood
they are just `std::optional`.

Note that use of `cached` and `latched` generally incurs a branch check for each
use.

`latched` does not provide for `reset`/`refresh`: it's intended for values that
are read once at startup time.

`cached` values can be `reset` or `refresh`ed. The default should probably be to
`reset` and lazily recompute but `refresh` is for the use case of "recompute
now".

### Other ideas

We don't use `stdx::optional` here; that requires tombstone value(s). Maybe
there is a way to select the optional implementation based on detecting a
tombstone? in practice many use cases for `cached`/`latched` are register
(`std::uint32_t`) reads rather than strong types that could use tombstones.
24 changes: 24 additions & 0 deletions docs/design/utility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# utility

Source code: https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/utility.hpp
Documentation: https://intel.github.io/cpp-std-extensions/#_utility_hpp

## sized

This exists so that we don't have to reason about "raw" size calculations
everywhere:

```cpp
// not this
auto size_in_dwords = (size_in_bytes + 3) / 4;
// but this
auto size_in_dwords = stdx::sized8{size_in_bytes}.in<std::uint32_t>();
```

More verbose, but almost certainly less error-prone and easier to read. These
are simple operations, and sometimes they should be terse. If the values are
known at compile time:

```cpp
constexpr auto size_in_dwords = 42_z8->z32;
```