Type Erasure: std::function, std::any and Building Your Own
Hiding a concrete type behind a uniform, value-semantic interface
std::function erases any callable with a given signature; std::any erases any copyable value. Both give you runtime polymorphism without a shared base class and with ordinary value semantics - you copy, move and store them like an int. Under the hood it is a small, disciplined marriage of templates and virtual dispatch, and once you see the pattern you can build your own.
- explain what "erasing" a type means and why value semantics matter,
- use
std::functionto store heterogeneous callables behind one signature, - use
std::anyandstd::any_castto hold and recover values of unknown type, - implement the Concept/Model idiom to build your own type-erased wrapper,
- reason about the cost: heap allocation, indirection, and the small buffer optimization.
1. The problem: polymorphism without a base class
You already know two ways to write code that works over many types. Templates give compile-time polymorphism: template <class T> stamps out a new function per type, with no runtime cost, but the type leaks into every signature - a std::vector<T> can hold only one T. Virtual functions give runtime polymorphism, but they are intrusive: every stored type must inherit from your base class and be handled through a pointer, so you lose value semantics and cannot store types you do not control (like int or a third-party struct).
Type erasure is the third way. It lets you write a single non-template class - say a Drawable - that can hold any type offering a draw() operation, without that type inheriting anything, and behaves like a plain value you can copy into a std::vector<Drawable>. The concrete type is "erased": after construction the wrapper only remembers how to draw, not what it holds.