Introduction
Classical object-oriented polymorphism in C++ relies on inheritance and virtual functions. While powerful, this approach forces all participating types into a common base class hierarchy, requires heap allocation, and introduces vtable indirection. Type erasure is an alternative technique that provides runtime polymorphism without inheritance — any type that satisfies a set of requirements (duck typing) can be used interchangeably, without modifying the type itself.
This article compares four leading C++ type erasure libraries: Microsoft Proxy, Dyno (by Louis Dionne), Boost.TypeErasure, and function2. Each takes a different approach to achieving polymorphic behavior without virtual inheritance.
What Is Type Erasure?
Type erasure is a C++ idiom where a concrete type’s interface is “erased” behind a wrapper that stores the object and dispatches calls through a vtable-like mechanism generated at the point of type erasure (not at the point of class definition). This gives you:
- Non-intrusive polymorphism: Types don’t need to inherit from a common base
- Value semantics: The erased wrapper can be stored by value, copied, and moved
- No heap allocation required: Small objects can use small buffer optimization (SBO)
- Better performance: No double indirection through base class pointers
The canonical example is std::function, which erases any callable type behind a uniform interface. Type erasure libraries generalize this pattern to arbitrary interfaces.
Library Comparison
| Feature | Microsoft Proxy | Dyno | Boost.TypeErasure | function2 |
|---|---|---|---|---|
| Stars | 3,045 | 1,036 | Part of Boost | 602 |
| C++ Standard | C++20 | C++14+ | C++11+ | C++11+ |
| Header-Only | Yes | Yes | Yes | Yes |
| Interface Definition | Macro-based PRO_DEF_MEM_DISPATCH | Concept-based with dyno::requires | Free-function-based BOOST_TYPE_ERASURE_MEMBER | Specialized for callables |
| Multiple Dispatch | Yes (facade pattern) | Yes | Limited | No (callables only) |
| Small Buffer Optimization | Configurable | Built-in | Configurable | Configurable |
| External Dependencies | None | Boost.Hana | Boost | None |
| Compile Time | Moderate | Slow (Hana-heavy) | Moderate | Fast |
Microsoft Proxy
Microsoft Proxy (also known as proxy) is the newest entrant, designed for C++20 and beyond. It introduces a “facade” pattern where you define a set of dispatch policies using macros, then use pro::proxy<Facade> as the erased type.
| |
Proxy supports multiple dispatch conventions (member functions, free functions, operators) within a single facade, making it the most feature-complete solution for general type erasure.
Dyno
Dyno, created by Boost.Hana author Louis Dionne, takes a concept-based approach. You define an interface using dyno::requires expressions and implement storage policies separately.
| |
Dyno’s design is elegant — it treats concepts as first-class types — but it relies heavily on Boost.Hana metaprogramming, which can slow compilation on large codebases. The project has been less actively maintained since 2021.
Boost.TypeErasure
Boost.TypeErasure is the most mature option, part of Boost since 2013. It uses a template-based interface definition where you specify requirements through template parameters.
| |
Boost.TypeErasure supports concept-based composition (combine multiple requirements through mpl::vector), reference semantics, and custom storage backends. The main drawback is verbosity — the BOOST_TYPE_ERASURE_MEMBER macro and mpl::vector syntax add boilerplate.
function2
function2 is a specialized type erasure library focused exclusively on callable objects. It provides fu2::function as a superior replacement for std::function, with key improvements:
| |
function2 handles move-only callables, overloaded signatures, configurable storage capacity, and const-correct invocation. It’s not a general type erasure framework, but for the specific use case of callable objects, it’s substantially better than std::function.
Why Choose Type Erasure Over Inheritance?
Type erasure and classical inheritance solve different problems, but in many modern C++ codebases, type erasure provides clear advantages:
- No intrusive hierarchy: External types, fundamental types, and lambdas can all participate without modification
- Value semantics: Erased wrappers are regular types — you can store them in vectors, return from functions, and copy them naturally
- Compile-time optimization: The dispatch mechanism is generated at the point of erasure, enabling better inlining than virtual calls
- No slicing: Since the wrapper owns the concrete object, there’s no risk of object slicing
For a deeper look at how these techniques integrate with modern C++ design patterns, see our C++ variant and sum type comparison. If you’re building dependency injection containers that leverage type erasure, our C++ DI container guide covers the patterns in depth. For type safety beyond polymorphism, check our strong type safety libraries comparison.
Building and Integration
All four libraries are header-only, making integration straightforward. Here’s how to set them up in a CMake project:
| |
Performance Considerations
Type erasure performance depends on several factors:
Storage strategy: Small Buffer Optimization (SBO) eliminates heap allocation for small objects. Proxy configures this per-facade, Dyno uses
sbo_storage<N>, function2 lets you set capacity via template parameter.Dispatch mechanism: All four libraries use a virtual-table-like approach, but the compiler can often devirtualize calls when the concrete type is known at compile time.
Copy cost: Type erasure copies involve invoking the stored type’s copy constructor through the vtable. For large objects, consider using reference semantics (
boost::type_erasure::any<Concept, boost::type_erasure::_self&>) or move-only wrappers.Compilation overhead: Dyno (Boost.Hana) has the heaviest compile-time cost. Proxy and function2 are relatively light. Boost.TypeErasure sits in the middle.
Performance Benchmarks and Scaling Considerations
When evaluating type erasure libraries for production use, several real-world performance factors matter more than microbenchmarks. The choice between Proxy, Dyno, Boost.TypeErasure, and function2 depends on your specific usage patterns.
For callable-heavy workloads where you primarily wrap functions and lambdas, function2 consistently outperforms both std::function and general-purpose type erasure by 20-40% due to its optimized storage layout and inlinable call operator. Proxy and Boost.TypeErasure add indirection for each virtual method call, similar to classic virtual function dispatch, making them suitable for interface-heavy patterns (3+ methods) but less optimal for single-method callables.
In multi-threaded scenarios, the thread safety guarantees differ substantially. Boost.TypeErasure provides explicit thread safety concepts (boost::type_erasure::thread_safe<>), while Proxy leaves thread safety to the underlying types. function2 guarantees const-correct invocation safety — a const function wrapper can only call const-qualified callables, preventing subtle data races during concurrent access. Dyno’s copy semantics are eager (deep copy via the stored type’s copy constructor), which can be expensive for large objects compared to reference-semantic alternatives.
Compilation time varies significantly: Dyno’s Boost.Hana dependency adds 2-5 seconds of template instantiation overhead per translation unit in medium-sized projects. Proxy and function2 add under 1 second. For large codebases with hundreds of translation units, this difference compounds — Proxy is the clear winner for CI pipeline speed while maintaining full type erasure capabilities.
FAQ
When should I use type erasure instead of virtual functions?
Use type erasure when the set of types that will satisfy your interface is not known in advance (e.g., third-party types, lambdas, standard library types), or when you want value semantics without heap allocation. Stick with virtual functions when you have a fixed, known hierarchy and deep inheritance chains with many overrides.
Can I use type erasure across DLL boundaries?
Be cautious. Type erasure wrappers store type information that is specific to the compilation unit. Copying or destroying an erased object across DLL boundaries can fail if the allocators or type_info differ. Boost.TypeErasure handles this best by decoupling concepts from storage, but careful testing is essential.
Is Microsoft Proxy production-ready?
Microsoft Proxy is actively maintained and used internally at Microsoft. It requires C++20 and a modern compiler (MSVC 2022+, Clang 15+, GCC 12+). The main limitation is that it’s still evolving — the API may change between versions.
Which library should I choose for a new project?
For general type erasure with C++20, Microsoft Proxy offers the best balance of features and compile-time performance. For C++14/17 projects, Boost.TypeErasure is the mature choice. If you only need type-erased callables, function2 is unquestionably better than std::function. Dyno is elegant but the heavy Boost.Hana dependency and reduced maintenance make it less suitable for production use.
How does type erasure compare to std::variant?
std::variant requires listing all possible types at compile time and stores one of them in a discriminated union. Type erasure allows any type satisfying the interface — you don’t need to enumerate them. See our variant and sum type guide for a detailed comparison.
💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com