"The stack" is a per-thread address space range, dynamically reserved by a kernel when a thread is created. The reason why "stack" is often presented as preferable to "heap" is that, when using a thread's stack, the expensive part of allocation - address space reservation, and preparation of physical pages for backing the address space - has already been performed when the thread was created.
But kernels also provide mechanisms for doing your own address space reservation (mmap, VirtualAlloc), and there is nothing stopping you from using these to do bulk allocations up-front to create your own stacks. This can make common case allocations as cheap as "the stack", but the advantage is that you now control the semantics and lifetime of the stack you've created. Thus, it does not need to be coupled to - for example - the lifetime of a scope or function, as the thread stack is.
The "stack versus heap" dichotomy is an unfortunate mythology because it seems to, in practice, communicate the idea that when a thread stack is insufficient for some purpose (allocations must exceed scope boundaries, allocations may need to exceed thread stack limits, allocations require more fine-tuned reserve/commit behavior, and so on), then the only alternative is the heap, particularly for very granular allocations.
This is, again, a mythology, and it has confused the C world in particular for decades.
std::vector always heap allocates. std::array can't change size. For decades, there's been no standard container that gives you a dynamically sized array with a compile-time capacity limit and zero heap allocation
C 26 finally adds std::inplace_vector. Guess where they got the idea 🧵👇