Type Erasure
TL;DR
- Both std::function and std::shared_ptr are implemented based on Type Erasure.
- Type Erasure = A technique that wraps various unrelated types in a single interface to handle them uniformly.
- std::function uses a Concept–Model structure to hide the calling interface (call operator).
- std::shared_ptr uses the same pattern to hide the deleter (how the destructor is invoked).
- Commonalities:
- They store the “original type information” internally to guarantee correct behavior at runtime (invocation/destruction).
- They internally cause virtual dispatch + heap allocation.
Overview
1) Why is std::function possible?
- A single type like
std::function<void(int)>can hold:- free functions
- lambdas
- functors
which are completely different types of objects.
- C++ is a strongly and statically typed language, yet this is possible because of Type Erasure.
2) Core Concepts of Type Erasure
| Component | Description |
|---|---|
| Concept (Interface) | A pure virtual base class that defines only the functions that need to be called. |
| Model<T> | A wrapper that wraps the type T and implements the Concept. |
| Container | Holds a Concept* as a member and executes the actual function of Model<T> via virtual dispatch upon invocation. |
In short:
Various types (T) → Wrap with Model<T> → Store as Concept* → Use as a common interface
This flow represents the identity of std::function.
3) Why std::shared_ptr uses the same technique
std::shared_ptr<void>is possible, butstd::unique_ptr<void>cannot delete the object due to its default deleter.
Why?
unique_ptr<void>→ The deleter isdefault_delete<void>, which makes it impossible to deletevoid*.shared_ptr<void>→ It stores the original object type T in the control block when created, allowing it to invoke T’s destructor when deleting.
That is, inside shared_ptr, there is Type Erasure structured as:
- Control block + virtual deleter.
4) The Cost of std::function
- Virtual call cost.
- Heap allocation cost.
- However, depending on the implementation, it may be stored on the stack via SBO (Small Buffer Optimization).
Example
1) std::function Type Erasure Structure (Simplified)
struct Concept {
virtual void call(int) = 0;
virtual ~Concept() = default;
};
template<typename T>
struct Model : Concept {
T obj;
Model(T o) : obj(o) {}
void call(int x) override { obj(x); }
};
class Function {
std::unique_ptr<Concept> ptr;
public:
template<typename F>
Function(F f) : ptr(std::make_unique<Model<F>>(f)) {}
void operator()(int x) { ptr->call(x); }
};
With this, all of the following work:
Function f1 = printNum;
Function f2 = [](int x){ std::cout << x; };
Function f3 = PrintNumFunctor{};
2) shared_ptr Type Erasure Structure
struct ControlBlockBase {
size_t refcount = 1;
virtual void destroy() = 0;
virtual ~ControlBlockBase() = default;
};
template<typename T>
struct ControlBlock : ControlBlockBase {
T* ptr;
ControlBlock(T* p) : ptr(p) {}
void destroy() override { delete ptr; }
};
shared_ptr stores:
T* obj_ptrControlBlockBase* ctrl
template<typename T>
class SharedPtr {
T* ptr;
ControlBlockBase* ctrl;
public:
template<typename U>
SharedPtr(U* p)
: ptr(p),
ctrl(new ControlBlock<U>(p)) {}
};
That is, thanks to the ControlBlock<U> storing the type U,
shared_ptr<void> can accurately delete the original type.
Takeaways
std::function
- A container that abstracts multiple types into a “callable object”.
- It hides type information using the Concept–Model–Container structure.
- Overhead: virtual call + heap allocation.
- However, its usability is powerful.
std::shared_ptr
shared_ptr<void>works safely because
→ it calls the deleter of the original object type when deleting the memory block.- This deleter is also a virtual-based Type Erasure structure.
- The reason
unique_ptr<void>cannot delete objects follows the same principle.
- Type Erasure plays a core role in various parts of the C++ Standard Library.
- A powerful pattern used to “handle heterogeneous types with a common interface.”
- Widely used in
std::function,std::shared_ptr,std::any,std::packaged_task, etc.
Reference
Unveiling C++ Type Erasure - From std::function to std::any - Sarthak Sehgal - C++Online 2025 CppNorth 2025, Sarthak Sehgal - Unveiling Type Erasure in C++: From std::function to std::any
Type erasure — Part I, Andrzej’s C++ blog Type erasure — Part II, Andrzej’s C++ blog Type erasure — Part III, Andrzej’s C++ blog Type erasure — Part IV, Andrzej’s C++ blog
Leave a comment