The facade pattern: extern "C" boundaries, opaque handle types, error codes at the edge, exceptions never crossing. Enum values and struct layout frozen by contract.
extern "C" {
typedef struct stb_ctx stb_ctx;
stb_ctx* stb_open(const char* dev);
int stb_ioctl(stb_ctx*, unsigned op, void* arg);
}
No change; C++ side wraps handles in unique_ptr with custom deleters immediately at the boundary.
struct CtxClose { void operator()(stb_ctx* c) const { stb_close(c); } };
using Ctx = std::unique_ptr<stb_ctx, CtxClose>;
[[nodiscard]] on wrapper results, string_view absorbing const char* + length pairs, noexcept now part of function types (relevant to callback pointer types).
std::span replaces (pointer, length) parameter pairs at the boundary; bit_cast handles layout-compatible conversions without aliasing UB.
std::out_ptr/inout_ptr adapt smart pointers to T** C factory conventions in one line — the biggest pure-interop QoL fix in years.
Ctx ctx;
if (stb_open2(dev, std::out_ptr(ctx)) != 0) fail();
#embed reaches C/C++ parity for binary resources, and trivial relocation formalizes which types can safely cross memcpy-based C APIs.
C++29direction · not adopted
Direction: the perennial ABI-stability-vs-progress debate; nothing concrete adopted.