std::optional
is going to require additional storage and fit fewer values into cache (it appears you already know the reason for this).
I don't think it's wrong to have a different value stored internally in your data structure from the one exposed by the public API, as long as the internal representation is completely hidden from users.
Furthermore, I suggest you isolate the magic number into a single pair of inline
conversion functions.
The compiler should help you remember to use the conversion functions consistently, by generating type errors if you forget. You might even use a thin struct wrapper for an int
in your internal data structure, to ensure that no implicit conversion exists (or define a user-defined conversion).
class CompressedOptionalUInt
{
static const unsigned SENTINEL_MISSING = std::numeric_limits<unsigned>::max();
unsigned value;
public:
CompressedOptionalUInt(std::optional<unsigned> val) : value(!val? SENTINEL_MISSING: *val) {}
operator std::optional<unsigned>() const { ... }
};
and then use std::array<CompressedOptionalUInt>
.
Making that into a template, with just the sentinel needing to be defined for each type, should be pretty straightforward.