diff --git a/docs/design/bit.md b/docs/design/bit.md new file mode 100644 index 0000000..9340ce6 --- /dev/null +++ b/docs/design/bit.md @@ -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(a, b, c, d); +auto [a_, b_, c_, d_] = stdx::bit_unpack(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(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 +``` diff --git a/docs/design/cached_latched.md b/docs/design/cached_latched.md new file mode 100644 index 0000000..df378de --- /dev/null +++ b/docs/design/cached_latched.md @@ -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. diff --git a/docs/design/utility.md b/docs/design/utility.md new file mode 100644 index 0000000..b65a063 --- /dev/null +++ b/docs/design/utility.md @@ -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(); +``` + +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; +```