5.2.28 · D5C++ Programming

Question bank — std - promise and std - future

1,149 words5 min readBack to topic

Two words we lean on throughout: the shared state is the heap-allocated control block (value slot, exception slot, ready flag, mutex, condition variable) that a promise writes and a future reads. "Move-only" means the object can be handed over with std::move but not copied.


True or false — justify

A std::promise<int> can be copied into two threads so both can fulfil it.
False. promise is move-only; copying would create two writers to one shared state, which is exactly the race the abstraction exists to prevent.
future::get() can be safely called twice to read the same value again.
False. get() moves out the result and invalidates the future; a second call throws future_error(no_state). Use std::shared_future for multiple reads.
Calling prom.get_future() twice on the same promise is legal and returns two futures.
False. The shared state has one reading end; the second call throws std::future_error(future_already_retrieved).
future::wait() consumes the value just like get().
False. wait() only blocks until ready and returns; the value stays in the shared state, so a later get() still works.
If you never call set_value and the promise is destroyed, get() simply blocks forever.
False. The promise's destructor stores a broken_promise error, so get() throws future_error(broken_promise) — it does not hang.
A std::future is copyable, so you can pass it to two consumers.
False. future is also move-only. For fan-out to many readers you convert it with share() into a std::shared_future.
set_value and set_exception may both be called on the same promise if you want a fallback.
False. The shared state is one-shot; the second set throws promise_already_satisfied. Choose exactly one outcome per path.
The condition-variable predicate in a hand-built version is optional if you only notify once.
False. condition_variable can wake spuriously, and the set may happen before the wait begins; the predicate re-checks ready to cover both.
fut.valid() returns true after a successful get().
False. get() invalidates the future, so valid() becomes false. It is true only while the future is still tied to a live shared state and not yet consumed.

Spot the error

std::thread t(worker, std::move(prom));
auto fut = prom.get_future();
``` ::: The promise was **moved first**, so the local `prom` has no shared state and `get_future()` throws. Grab the future **before** moving.
 
```cpp
std::thread t(worker, prom); // prom is std::promise<int>
``` ::: `promise` is non-copyable, so this fails to compile (or would duplicate a writer). Wrap with `std::move(prom)`.
 
```cpp
void worker(std::promise<int> p) { throw std::runtime_error("boom"); }
``` ::: The exception escapes the thread function → `std::terminate`. Catch it and ship it: `p.set_exception(std::current_exception())`.
 
```cpp
int x = fut.get();
int y = fut.get(); // read it again
``` ::: The first `get()` moved the value out and invalidated the future; the second throws `future_error(no_state)`. Store `x` and reuse it.
 
```cpp
p.set_value(1);
p.set_exception(std::current_exception());
``` ::: Only one outcome allowed. The second call throws `promise_already_satisfied`. Use `try/catch` so exactly one of the two runs.
 
```cpp
std::promise<int> prom;
auto fut = prom.get_future();
// ... path where an early 'return' skips set_value ...
``` ::: On the early-return path the promise dies unset, so the waiting `get()` throws `broken_promise`. Fulfil the promise on **every** control-flow path.
 
---
 
## Why questions
 
Why can't a plain [[std - thread|std::thread]] just return a value to its creator? ::: `thread`'s callable return value is discarded; there is no built-in channel back. `promise`/`future` (or [[std - async and std - launch|std::async]]) supplies that channel.
 
Why is the exception transported as a `std::exception_ptr` and not the exception object itself? ::: The type is unknown at the boundary and the object must survive being moved across threads; `exception_ptr` type-erases and reference-counts it so `get()` can re-throw the exact original via `rethrow_exception`. See [[Exception handling in C++]].
 
Why does the producer lock a [[std - mutex and lock_guard|mutex]] just to set `ready = true`? ::: Writing `value`/`ready` in one thread while another reads them is a data race (undefined behaviour); the mutex plus the notify establishes the happens-before ordering that makes the read safe.
 
Why prefer `promise`/`future` over a global variable with a `bool ready` flag? ::: The library packages the mutex, condition variable, ready flag **and** exception transport correctly and portably, eliminating the hand-rolled race and missed-wakeup bugs.
 
Why does `get()` **move** the value out instead of copying it? ::: The channel is one-shot with a single owner of the result, so moving is both cheaper and semantically honest — after consumption there is nothing to read again. See [[Move semantics and std::move]].
 
Why can `set_value` run entirely **before** the consumer ever calls `wait`? ::: The two threads are unsynchronized in wall-clock time; the ready flag captures "already done" so `wait(lk, pred)` returns immediately instead of sleeping and missing the notify.
 
---
 
## Edge cases
 
What does `get()` do if `set_exception` was used instead of `set_value`? ::: It re-throws the stored exception in the **consumer's** thread, so you handle it where you wait rather than where it was thrown.
 
What is returned by `future::valid()` right after `get_future()` but before any `set`? ::: `true` — the future is associated with a live shared state; it just blocks on `get()` until a value or exception arrives.
 
If both `set_value` and the promise's destructor run, does the destructor overwrite with `broken_promise`? ::: **No.** Once satisfied, destruction is a clean no-op for the state; `broken_promise` is stored only if the state was still **unsatisfied** at destruction.
 
Is it legal to move a promise, fulfil it in the new thread, and let the original (now empty) go out of scope? ::: **Yes.** The moved-from promise holds no shared state, so its destruction does nothing; the moved-to promise owns and fulfils the state normally.
 
What happens if `get()` is called on a `future` that was moved from? ::: It throws `future_error(no_state)` — the moved-from future has no association. Guard with `valid()` if unsure.
 
Can a `promise<void>` exist, and what does its `get()` return? ::: **Yes.** `promise<void>`/`future<void>` carry no value — `set_value()` takes no argument and `get()` returns nothing; they exist purely to signal "done" or transport an exception.