Skip to content

Latest commit

 

History

History
190 lines (135 loc) · 9.02 KB

File metadata and controls

190 lines (135 loc) · 9.02 KB
layout default
class overload-slide

overload pattern: cleaner std::visit

The if constexpr ladder (what the talk uses):

std::visit([](auto&& state) {
    using T = decay_t<decltype(state)>;
    if constexpr (is_same_v<T, NewOrder>) { ... }
    else if constexpr (is_same_v<T, Working>) { ... }
    else if constexpr (is_same_v<T, Filled>) { ... }
}, order.status);

The overload pattern (alternative):

template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>; // not needed in C++20

std::visit(overload{
    [](const NewOrder& s)  { ... },
    [](const Working& s)   { ... },
    [](const Filled& s)    { ... },
}, order.status);
  • overload uses normal overload resolution — no is_same_v or decay_t
  • Each lambda takes the concrete type directly — cleaner, no auto&& gymnastics
  • if constexpr ladder is more familiar; overload is closer to pattern matching

layout: default

std::execution & senders/receivers

  • C++26: structured async programming model (P2300)
  • Sender: describes a computation that will eventually produce a value
  • Receiver: handles the result (value, error, or cancellation)
  • Analogous to our .and_then pipeline — but for async and concurrent work
auto pipeline =
    ex::just(order)
    | ex::then(enrichWithMarketData)
    | ex::then(validateOrder)
    | ex::on(exchange_thread, applyFills);
  • Composable, lazy, zero-overhead abstraction
  • Cancellation and error propagation are first-class

layout: default

When NOT to modernize

Hot paths with tight latency budgets:

  • std::function virtual dispatch: ~1–5ns per call — measurable at nanosecond scale
  • Prefer templates or raw function pointers in the critical path
  • Profile first: perf, VTune, or Tracy before assuming overhead

Embedded / -fno-rtti / -fno-exceptions environments:

  • std::function may be unavailable or too heavy
  • std::variant + std::visit require RTTI in some implementations
  • Use simpler tagged union or template-based dispatch instead

Very small, stable, self-contained modules:

  • A 200-line class that never changes and has no dependents — leave it alone
  • Modernization has a cost: review time, testing, cognitive load for readers

layout: default

Thread safety: pure functions + OrderContext

  • Pure functions are inherently thread-safe — no shared mutable state
  • OrderContext: read-only infrastructure bundle — safe to share across threads if members are const-correct
  • std::expected return values: each thread gets its own value — no sharing
  • vector<Transform> pipeline: safe to share if built before threads start, read-only after

Some caveats to pay attention to:

  • OrderContext members that lazily cache (e.g., mutable cache fields) need synchronization
  • std::function itself is not thread-safe to copy/assign concurrently
  • Pipelines that accumulate into shared state need explicit coordination

layout: default

ABI stability & type erasure

  • ABI (Application Binary Interface): binary contract between compiled units — vtable layout, name mangling, calling conventions
  • Adding a method to a base class breaks ABI — all subclasses must recompile
  • Type-erased wrappers (AnyOrderProcessor, std::function) stabilize ABI at the wrapper boundary
  • Concrete types behind the wrapper can change freely without recompiling callers
  • std::function<void(const Order&)> is a stable symbol — swap the implementation, callers unchanged

Watch out for:

  • std::expected, std::variant layout changes between standard library versions
  • Mixing compilers or STL versions across shared library boundaries

layout: default

std::function overhead

  • Stores any callable matching a signature via type erasure
  • Small Buffer Optimization (SBO): small callables (≤ ~16–32 bytes) stored inline — no heap allocation
  • Large captures or stateful functors heap-allocate on construction
  • Every call goes through a virtual dispatch indirection (function pointer table)
  • std::move_only_function (C++23): no copy requirement, better for unique ownership
std::function Raw pointer Template
Heap alloc Maybe No No
Overhead Virtual dispatch None Inlined
Flexibility Any callable Fixed sig Fixed at compile time