Visual walkthrough — noexcept specifier
This page rebuilds the parent noexcept idea from the ground up, in pictures. We start with a totally empty box — a piece of memory — and watch, step by step, why the compiler and std::vector end up caring about a single promise. Every symbol is earned before it is used.
Step 1 — What "an exception escaping" even looks like
WHAT. A function is just a room you walk into, do some work, and walk out of. Normally you leave through the front door (return). An exception is an alternate exit — a trapdoor that yanks you out sideways, skipping the rest of the work.
WHY show this first. Everything about noexcept is a claim about which exits are allowed. We cannot talk about the promise until we can picture the two doors.
PICTURE. The room has two ways out. The black arrow is the normal return. The red arrow is the exception trapdoor — the thing noexcept promises will never be used.

Step 2 — The promise, drawn as a sealed door
WHAT. Writing noexcept after a function's signature welds the red trapdoor shut. You are telling the compiler: "this room has only the front door."
void f()— the function (the room).noexcept— the specifier; it seals the exception exit. It is exactlynoexcept(true).
WHY. Once the compiler believes the trapdoor is gone, it can throw away the cleanup scaffolding it would otherwise build around the room to safely handle a sideways exit. Less scaffolding = smaller, faster code. That saving is the entire reward for making the promise.
PICTURE. Same room as Step 1, but the red trapdoor is now bolted shut. The scaffolding around it (the dashed cleanup machinery) has been removed.

Step 3 — Breaking the promise: the terminate landmine
WHAT. Suppose you sealed the trapdoor (noexcept) but then, inside the room, you throw an exception that tries to escape anyway. The exception rushes toward an exit that no longer exists — the cleanup scaffolding was deleted in Step 2. There is nowhere for it to go.
WHY. The runtime cannot let a half-escaped exception wander through a room with no unwinding information — that is undefined chaos. So it does the only safe thing: it detonates.
- left side — the broken promise (a real escape, not a caught one).
- right side — std::terminate fires: the whole program dies immediately, with no guarantee of running destructors above this point.
PICTURE. The exception (red) slams into the welded trapdoor. Because the escape route is gone, the impact triggers std::terminate() — the program ends.

Step 4 — Asking the promise a question: the noexcept operator
WHAT. The same word has a second job. noexcept(expr) is a question the compiler answers at compile time: "is this expression guaranteed not to throw?" It hands back a bool.
expr— a piece of code, but it is unevaluated (likesizeof): nothing actually runs.- the result is a compile-time value, so it can feed a
constexpr(see constexpr).
WHY. We need a way for generic code to look at some type T and ask "can I trust your operations?" without running them. The operator is that inspection tool.
PICTURE. A magnifying glass (red) hovers over a function and reads its door: sealed → answers true; open trapdoor → answers false.

Step 5 — The scene where it pays off: a vector runs out of room
WHAT. A std::vector keeps its elements in one solid block of memory. When it fills up, it must grab a bigger block and transfer every element across.
WHY set this up. This transfer is the exact moment where the promise from Step 2 changes the machine's decision. We first need to see the transfer to see the danger.
PICTURE. The old buffer (black, full) and a new, larger buffer (black, empty). Elements must travel from old to new. The element in flight is the object we are about to worry about (red).

Step 6 — The danger: a throwing move mid-transfer
WHAT. To transfer, the vector can move each element (steal its guts — cheap; see Move semantics) or copy it (duplicate — costly). Suppose it moves and, halfway through, one move throws.
WHY this is a disaster. Now some elements live in the new buffer, and the originals in the old buffer were already gutted by the move. There is no clean state to fall back to — the data is torn in half. This breaks the Strong exception guarantee (the rule that a failed operation must leave things exactly as they were).
PICTURE. Mid-transfer, element 3's move throws (red burst). Left buffer: gutted originals. Right buffer: a few survivors. The dashed line marks the corruption — no way back.

Step 7 — The fix the vector chooses, decided by the promise
WHAT. So the vector asks the Step-4 question about T's move constructor:
T(std::move(x))— constructing aTby moving fromx.- the operator returns the promise on
T's move constructor. true→ moving cannot throw → safe to gut originals → move.false→ a move might throw mid-transfer (Step 6) → keep originals intact → copy, and destroy originals only after all copies succeed.
WHY. If moving can never throw, the Step-6 disaster is impossible, so the vector fearlessly takes the fast lane. If it might throw, the vector plays safe: copies leave the originals untouched, so a thrown copy loses nothing. This exact decision is bottled into the standard helper std::move_if_noexcept.
PICTURE. A fork in the road. The red signpost is the noexcept answer; true steers onto the MOVE track, false onto the COPY track.

Step 8 — The degenerate cases (so no scenario surprises you)
WHAT & WHY. Corners the fast/slow story must also survive:
- Empty vector (0 elements): reallocation transfers nothing, so the move-vs-copy choice never fires. Both branches do the same "nothing." No danger, no decision.
- A type with no move constructor (only a copy): the
noexcept(...)question about the move falls back to copying anyway — same safe track asfalse. - Throw caught inside the move: if
T's move throws internally but catches it and is declarednoexcept, the operator returnstrue— the promise is what's read, not the internals. (If it lies and escapes, Step 3's landmine fires.) - A
noexcept(false)move that never actually throws: still read as "might throw," so the vector copies. The promise, not the runtime behaviour, drives the choice — an honestnoexceptis what unlocks speed.
PICTURE. Four tiny panels: empty box (no transfer), copy-only type, caught-inside, and the honest-but-unmarked type — each routed to its track, the deciding label in red.

The one-picture summary
One diagram compresses the whole chain: seal the trapdoor → compiler drops scaffolding → the operator can read the seal → vector takes the fast MOVE track. Break the seal and the same path leads to std::terminate().

Recall Feynman retelling — the whole walkthrough in plain words
A function is a room with two exits: the normal door and a secret trapdoor for emergencies (exceptions). Saying noexcept welds the trapdoor shut, and because the builder trusts there's no trapdoor, he removes all the safety scaffolding around it — that's why your code gets leaner and faster. But if an emergency does try to use the sealed trapdoor, there's no safe way out, so the building is demolished on the spot (std::terminate). Now picture a vector that outgrew its box and must carry every item to a bigger box. Carrying by "moving" is fast but empties the old box as you go — if you drop something halfway, both boxes are ruined. So before choosing, the vector reads the seal on each item's move: sealed (noexcept true) means dropping is impossible, so it moves fearlessly and fast; unsealed means it plays safe and copies, keeping the originals until every copy lands. One honest promise, and the whole machine speeds up.