Ch9.16: C++ Standard Library Containers
Overview
The C++ standard library ships its own set of containers in the
std:: namespace. If you are coming from another language or
have not used C++ before, you may encounter these in existing codebases.
This section introduces them and explains how they differ from
fast_io.
In short: prefer fast_io containers. They
are safer, faster, and more consistent. But you should still recognise
the std:: equivalents so you can read other people’s
code and understand the ecosystem.
1. Available Containers
std:: container |
fast_io:: equivalent |
Description |
|---|---|---|
std::vector<T> |
::fast_io::vector<T> |
Dynamic contiguous array. |
std::deque<T> |
::fast_io::deque<T> |
Double-ended queue (block-map layout). |
std::list<T> |
::fast_io::list<T> |
Doubly-linked list. |
std::forward_list<T> |
::fast_io::forward_list<T> |
Singly-linked list. |
std::array<T, N> |
::fast_io::array<T, N> |
Fixed-size array. |
std::string |
::fast_io::string |
Dynamic, null-terminated character sequence. |
std::string_view |
::fast_io::string_view |
Non-owning view over characters. |
std::span<T> |
::fast_io::span<T> |
Non-owning view over a contiguous sequence. |
std::stack<T> |
::fast_io::stack<T> |
LIFO adaptor. |
std::queue<T> |
::fast_io::queue<T> |
FIFO adaptor. |
std::priority_queue<T> |
::fast_io::priority_queue<T> |
Priority-based adaptor. |
2. Basic Usage
The std:: containers are used in much the same way as
fast_io ones. The main differences are in the header paths
and the lack of some fast_io-specific features:
#include <vector>
#include <deque>
#include <string>
#include <iostream>
int main() {
// std::vector — dynamic contiguous array
::std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
::std::cout << "Size: " << v.size() << '\n';
// std::deque — double-ended queue
::std::deque<int> d;
d.push_front(10);
d.push_back(20);
// std::string — dynamic character sequence
::std::string s = "Hello";
s += ", World!";
::std::cout << s << '\n';
}
fast_io
throughout. You do not need to learn the std:: containers
to follow along — everything we cover uses fast_io.
3. Key Differences
If you do encounter std:: containers, be aware of these
differences:
| Aspect | std:: containers |
fast_io:: containers |
|---|---|---|
| Bounds checking | operator[] is unchecked — out-of-bounds is undefined behaviour. at() throws std::out_of_range, but this adds significant overhead for bounds checking. Worse, an out-of-bounds access is already a bug — catching an exception at that point does not fix the underlying problem. Trying to recover from a program bug makes things worse compared to just terminating and restarting. |
operator[] is bounds-checked. Out-of-bounds calls fast_terminate() immediately — deterministic, no exception overhead, no false sense of recovery. |
| Allocation failure | Throws std::bad_alloc. A failed heap allocation means the abstract machine is corrupted — there is no meaningful way to continue. Throwing an exception invites buggy recovery code that tries to limp along on a broken heap. |
Calls fast_terminate() — no exception unwinding, no attempt to recover from an unrecoverable state. The program is already corrupt; crash deterministically. |
| Logic bugs (empty front/back, OOB) | Undefined behaviour. Silent memory corruption in release builds. | Deterministic termination. Bugs are caught immediately. |
| Growth relocation | Uses move constructors. For types with non-trivial moves, this can be slow. | Uses memcpy when types are trivially relocatable — much faster. |
| Index-based operations | No insert_index / erase_index. Must use iterators. |
Bounds-checked insert_index / erase_index on all eligible containers. |
| String formatting | std::format (C++20) or std::stringstream — relatively slow. |
concat_fast_io + to<T> — compile-time optimised. |
| Unchecked variants | None. Only operator[] is unchecked; everything else is checked via exceptions. |
Explicit _unchecked variants for performance-critical code. |
| Bit vector | std::vector<bool> is a specialisation that packs bits but has a broken interface (proxy references). |
::fast_io::bitvec is a separate, clean type with proper bit-level operations. |
4. A Note on C++26 Hardening
C++26 introduces hardening for standard library
containers — bounds checks on operator[],
front(), back(), and iterator validity
assertions. However, these containers were not designed to be
hardened from the ground up. Retrofitting safety checks onto decades of
existing ABI leads to problems:
- ODR violations — translation units compiled with different hardening levels see different class layouts, violating the One Definition Rule.
- Inconsistent coverage — some checks are opt-in, some are implementation-defined, and the exact set of checks varies between standard library vendors.
- Still no index-based API — hardening adds
checks but does not add
insert_index/erase_index. You still need iterators for index-based manipulation.
fast_io containers were designed with safety from the
start — no ODR issues, no opt-in confusion, and consistent
deterministic termination across all builds.
5. Code Comparison
Here is the same program written with both libraries:
With fast_io (preferred)
#include <fast_io_dsal/vector.h>
#include <fast_io_dsal/string.h>
#include <fast_io.h>
int main() {
::fast_io::vector<::std::size_t> v;
v.push_back(1zu);
v.push_back(2zu);
v.push_back(3zu);
// Bounds-checked — terminates on out-of-bounds
::fast_io::io::println("v[0] = ", v[0zu]);
// Index-based insert and erase
v.insert_index(1zu, 99zu); // [1, 99, 2, 3]
v.erase_index(0zu); // [99, 2, 3]
// String formatting
::fast_io::string s = ::fast_io::concat_fast_io("Count: ", v.size());
::fast_io::io::println(s);
}
With std::
#include <vector>
#include <string>
#include <format>
#include <iostream>
int main() {
::std::vector<::std::size_t> v;
v.push_back(1zu);
v.push_back(2zu);
v.push_back(3zu);
// operator[] is unchecked — UB if out of bounds
::std::cout << "v[0] = " << v[0] << '\n';
// v.at(0) is checked but throws an exception
// Must use iterators for insert/erase
v.insert(v.begin() + 1, 99zu); // [1, 99, 2, 3]
v.erase(v.begin()); // [99, 2, 3]
// String formatting
::std::string s = ::std::format("Count: {}", v.size());
::std::cout << s << '\n';
}
6. Further Reading
For full documentation of the std:: containers, see
cppreference — Container library. It provides exhaustive
reference material for every std:: type, method, and
overload.
However, for new code in this tutorial’s projects, always reach
for the fast_io equivalent first.
Key takeaways
- C++ ships its own containers in
std:::vector,deque,list,forward_list,array,string,stack,queue,priority_queue, etc. - Their usage patterns are similar to
fast_io— but they lack bounds checking, index operations, and deterministic termination. std::vector<bool>is a broken specialisation; use::fast_io::bitvecinstead.- C++26 adds hardening to
std::containers, but retrofitting safety onto existing ABIs causes ODR violations and inconsistent coverage.fast_iowas designed safe from the start. - Prefer
fast_iocontainers for safety, performance, and consistency. - For
std::container documentation, see cppreference.