Modern C++ compilers reliably devirtualize calls to final methods but diverge significantly on corner cases involving dataflow, internal linkage, and conditional casts.
Key Takeaways
GCC, Clang, MSVC, and ICC all handle the trivial case (known concrete type on stack) but MSVC and ICC fail on simple Base* cast scenarios.
Marking a class or method final is the most portable devirtualization hint; GCC alone detects leafness via internal-linkage member types.
Anonymous namespace classes (internal linkage) can enable devirtualization within a single TU – a practical pattern for pImpl-style implementations.
GCC handles Derived::f defined directly but often misses inherited Derived::g – filed as GCC bug #99093.
LTO (link-time devirtualization) is explicitly out of scope; all results are single-TU, compiler-only analysis.
Hacker News Comment Review
Commenters agree devirtualization is too fragile to rely on in performance-critical paths; the practical advice is to avoid virtual calls there entirely or annotate explicitly with final.
A Clang crash was reported where profile-guided speculative devirtualization combined with BOLT’s identical code folding (ICF) produced incorrect vtable pointer validation – illustrating real production risk.
Rust’s dyn Trait devirtualization came up as a related open question; consensus is it’s less common in Rust due to ergonomic costs of Box<dyn>, reducing the problem surface compared to C++.
Notable Comments
@fibonacci112358: MSVC once had advanced inter-procedural devirtualization via LTO but it was disabled for being too buggy and slow, especially with DLLs injecting new derived classes.
@lowbloodsugar: Java’s JIT speculatively devirtualizes on call-site type profile, collapsing five or six virtual ByteBuffer calls into a single store instruction.