42. Zero-cost CRTP
In a matching engine hot path, virtual calls can cause mispredictions and extra loads that cost nanoseconds. CRTP enables static dispatch and inlining without vtables or RTTI. A teammate proposes this design:
template<class D>
struct FeedHandler {
void on_msg(int x) { static_cast<D*>(this)->impl(x); }
};
struct MyHandler : FeedHandler<MyHandler> {
void impl(int) noexcept {}
};
Part 1.
Does this achieve zero-overhead dispatch versus a virtual call in hot paths? Explain the correctness requirements to avoid UB and extend it for const-correct invocation.
Part 2.
(1) When is static_cast<D*>(this) well-defined? Consider inheritance and object lifetime.
(2) Will on_msg inline across TUs? What blocks inlining?
(3) How does noexcept on impl influence codegen on x86-64 SysV?
(4) Does CRTP enable empty base optimization here? Any size or alignment pitfalls?
(5) How to make on_msg callable on const objects without duplication?
Answer
Answer (Part 1)
Yes; the call resolves at compile time to D::impl, eliminating vtable lookups and enabling inlining for near zero overhead. It is well-defined only when the dynamic type of *this is exactly D (or derived) and the CRTP base is an accessible, unambiguous non-virtual base; otherwise dereferencing the downcast is UB. For const-correctness, declare on_msg as const and dispatch via a const D*, requiring a matching impl(int) const (or provide both const/non-const overloads).
Answer (Part 2)
(1) It’s valid when the actual object is a D (or derived) and the base subobject is unambiguous and accessible. If the dynamic type isn’t D, or lifetime has ended, dereferencing is UB.
(2) Yes if the definition is visible and heuristics allow; headers help. Across TUs often needs LTO or always-inline hints; large bodies or ODR barriers prevent it.
(3) It removes exception edges and lets the compiler assume no throwing, simplifying the fast path. Calling convention is unchanged on SysV; unwind metadata may remain unless exceptions/unwind tables are disabled.
(4) Yes; the empty FeedHandler<D> base is elided via EBO, not increasing size. Pitfalls include virtual inheritance, multiple identical empty bases, or ABI constraints that introduce padding/alignment.
(5) Make on_msg a const member and downcast to const D*, requiring a const impl. Alternatively, provide both const and non-const impl overloads.