C++ Interview: How Does std::inplace_vector's Fixed Capacity Change the Design?
Prompt and scope
Explain how C++26 std::inplace_vector<T, N> provides variable length with fixed-capacity contiguous storage inside the object. Design a safe append policy covering overflow, exceptions, moves, iterator invalidation, and when std::vector remains the right choice.
std::inplace_vector is a C++26 contiguous container whose size varies while storage lives inside the object and capacity is fixed by the non-type template parameter N. It fits a known upper bound and a no-allocation path, but it is not std::array and cannot grow without bound. The answer should center on capacity contracts and object lifetime rather than syntax.
What the interviewer evaluates
- Separating size, capacity, in-object storage, and dynamic allocation.
- Explaining overflow semantics at capacity
Nand the error policy. - Understanding contiguous storage, moves, references, and iterator invalidation.
- Discussing element-construction exceptions and whether a strong guarantee is possible.
- Recognizing how
Naffects object size, stack layout, and ABI. - Choosing among
inplace_vector,array,vector, and other containers.
Clarifying questions to ask
- Is
Na provable compile-time bound or runtime configuration? - Is overflow a programmer error, recoverable input, or a message to drop?
- Are elements movable, copyable, or potentially throwing?
- Does the container live on the stack, in a pool, shared memory, or a hot-path object?
- Do callers retain element references, pointers, or iterators?
30-second answer
I would make N an explicit capacity contract and choose an overflow policy—reject, return an error, or apply backpressure—never implicit growth. inplace_vector keeps contiguous access but stores its buffer in the object, so object size and move cost grow with N. Check size before append and use a construction path that matches the element's exception guarantees. Insertions can still invalidate references and iterators even without heap reallocation. For unknown bounds or amortized growth, I would use std::vector.
Step-by-step deep dive
1. State capacity and lifetime
The size of std::inplace_vector<T, N> is between zero and N, and elements occupy contiguous addresses. N is part of the type. Construction does not default-construct all N elements; elements are constructed on insertion and only live elements are destroyed.
2. Define an append contract
Check size() == capacity() before appending and map overflow to a business error, drop policy, or upstream backpressure. Do not treat reserve as expansion or silently overwrite the tail. For batch appends, either check remaining capacity first or define partial success and return the inserted count.
template<class T, std::size_t N>
bool try_append(std::inplace_vector<T, N>& out, T value) {
if (out.size() == out.capacity()) return false;
out.push_back(std::move(value));
return true;
}The example expresses the capacity policy only. The real return and state guarantee depend on whether T's move construction can throw.
3. Handle exception safety
If element construction or moving can throw, preserve container invariants and state whether the operation offers the basic or strong guarantee. A batch can construct in a temporary container and then move, but that move can also throw; the type name does not promise transactional commit.
4. Discuss references and iterators
Inserting an element can move later elements, so saved references, pointers, and iterators must follow the standard invalidation rules. No heap reallocation does not mean positions never change. An API can return an index or stable handle instead of encouraging callers to retain element addresses.
5. Compare object size and move cost
The in-object buffer means sizeof(inplace_vector<T, N>) generally grows with N and element alignment. Putting a large capacity in a stack frame, message object, or frequently copied structure can increase stack, cache, and move costs. Measure layout before claiming that avoiding allocation is always faster.
6. Choose alternatives
Choose inplace_vector for a known bound, contiguous access, and a desired no-allocation path. Use std::array for a fixed number of elements, std::vector for unknown bounds or growth, and another container when stable node addresses matter. Capacity, lifetime, locality, and error semantics decide.
7. Test and observe
Test empty, exactly full, over-capacity, throwing elements, moves and copies, nested objects, and large N. Record overflow count, partial batch success, object size, and hot-path latency; use sanitizers for lifetime errors. When the compiler and library support C++26, verify feature-test macros and implementation differences.
High-quality sample answer
I would treat N as a compile-time capacity contract: size varies but cannot exceed N, and elements are contiguous inside the object. Check remaining capacity before append and map overflow to an error or backpressure instead of overwriting. Define the basic or strong guarantee for potentially throwing elements and state whether a batch can partially succeed. References and iterators are not automatically stable just because there is no heap growth.
I would also measure object size, stack and cache pressure, move cost, and ABI. With a known bound and contiguous hot-path access, inplace_vector can fit; with unknown bounds or amortized growth, use std::vector, and use std::array for a fixed count. Test capacity and exception boundaries and observe overflow and latency.
Common mistakes
- Treating it as a vector that grows → it stops at N → state overflow semantics.
- Assuming in-object storage keeps references stable → element movement changes positions → follow invalidation rules.
- Assuming all elements are constructed → only current size is live → separate storage from lifetime.
- Focusing on zero allocation while ignoring object size → large N pressures stack and cache → measure layout and moves.
- Assuming batch exceptions roll back automatically → element moves can throw → state and test the guarantee.
- Forcing it onto an unknown bound → business input gets rejected or truncated → choose vector or another container.
Follow-up questions and responses
How does inplace_vector<T, N> differ from std::array<T, N>?
Its size varies from zero to N and elements are constructed as needed; an array always contains N elements. Both use in-object storage, but their lifetime and API contracts differ.
Should full capacity throw?
That is a business choice. Recoverable input usually returns an error or applies backpressure; a programmer error may use an assertion or exception. Silent overwrite is not acceptable, and batch partial-success semantics must be explicit.
What happens when an inplace_vector is moved?
Elements are moved or copied into the destination object's buffer, with cost tied to size and element type. It is not merely swapping a heap pointer.
Why not choose a very large N?
The buffer increases object, stack, copy, and cache costs. Choose N from actual distribution, tail capacity, and overflow cost.
What if the standard library lacks C++26 support?
Check feature-test macros and implementation documentation, set a clear build requirement or choose an alternative. Do not silently treat an experimental implementation as standard behavior.
How do you keep element references safe?
Limit reference lifetimes, prefer indexes or stable handles, and prohibit old references after insertion, moves, or destruction. Validate with sanitizers and boundary tests.