Plast interview

4. Strong enums

In a feed-handler hot path, we compact flags and sides to fit more messages per cache line while preserving type safety. Scoped enums with explicit underlying types help, but ABI layout and aliasing rules still matter for latency and correctness.

#include <cstdint>
enum class Side : uint8_t { Buy=0, Sell=1 };
struct Packet { uint32_t px; uint32_t qty; Side side; };
void encode(uint8_t* dst, const Packet& p) noexcept {
    dst[0]=static_cast<uint8_t>(p.side); 
}

Part 1.

On x86-64, what are the likely sizeof(Packet) and alignof(Packet)? How would you keep strong typing yet serialize side compactly with minimal overhead?

Part 2.

(1) How does uint8_t as underlying type affect Packet size and alignment across common ABIs?

(2) Prefer std::to_underlying(p.side) or static_cast<uint8_t>(p.side) for serialization? Why?

(3) Any aliasing concerns writing through uint8_t* when serializing Side?

(4) Should encode be constexpr or only noexcept? When is constexpr valid here?

(5) How to map Side to branchless arithmetic without UB or layout penalties?

Answer

Answer (Part 1)

sizeof(Packet) is typically 12 and alignof(Packet) is 4 on x86-64 System V and MS x64 ABIs. Keep Side as enum class : uint8_t for strong typing in the in-memory struct, and serialize explicitly by writing the byte (static_cast<uint8_t>(p.side) or std::to_underlying) rather than relying on struct layout.

Answer (Part 2)

(1) It shrinks the logical payload to 9 bytes but the struct rounds to 12 due to padding; alignment remains 4. Different ABIs may still align to the largest member, so expect padding unless packed (often undesirable).

(2) std::to_underlying is clearer and safer against signedness mistakes; it conveys intent. static_cast<uint8_t> is fine pre-C++23 but easier to misuse if the underlying type changes.

(3) Character types (char/unsigned char/std::byte) may alias any object, so writing a byte is permitted. The reverse—treating raw bytes as Side—should use std::bit_cast or explicit conversion from the byte.

(4) constexpr is only valid if arguments are constant-evaluated and there’s no indirection side-effects; here the pointer store forbids it. noexcept is appropriate and can enable minor optimizations by ruling out unwinding.

(5) Map to {+1,−1} via a lookup array or bit trick on the underlying byte, then multiply a delta. Ensure the arithmetic type is signed to avoid unsigned underflow, and keep Side storage unchanged.