The shared state co-owned by shared pointers also contains a deleter, a function like object that is fed the managed object at the end of its lifetime in order to release it. We can even specify our own deleter by using the appropriate constructor. How the deleter is stored, as well as any type erasure it undergoes is an implementation detail. But suffice it to say that the shared state contains a function that knows exactly how to free the owned resource.
Now, when we create an object of a concrete type with make_shared<Thing>() and don’t provide a deleter, the shared state is set to hold some default deleter that can free a Thing. The implementation can generate one from the template argument alone. And since its stored as part of the shared state, it doesn’t depend on the type T of any shared_pointer<T> that may be sharing ownership of the state. It will always know how to free the Thing.
So even when we make voidPtr the only remaining pointer, the deleter remains unchanged, and still knows how to free a Thing. Which is what it does when the voidPtr goes out of scope.