#include
#include
#include
int main() {
std::vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Filter even numbers and square them
auto result = numbers |
std::views::filter([](int n) { return n % 2 == 0; }) |
std::views::transform([](int n) { return n * n; });
std::cout << "Filtered & Mapped: ";
for (int v : result) {
std::cout << v << " ";
}
// List comprehension equivalent
auto comprehension = std::views::iota(1, 10) |
std::views::transform([](int n) { return n * n; });
std::cout << "\nComprehension: ";
for (int v : comprehension) {
std::cout << v << " ";
}
}
|
for compositionstd::views::filter
and std::views::transform
create lazy-evaluated viewsstd::views::iota
generates number sequencesstd::async
#include
#include
#include
template<typename T>
class LazyFuture {
std::function<T()> func;
public:
LazyFuture(auto f) : func(f) {}
T get() { return func(); }
};
int main() {
// Lazy future
LazyFuture<int> lazy([]{
std::cout << "Calculating...\n";
return 42;
});
std::cout << "Lazy created\n";
std::cout << "Value: " << lazy.get() << "\n";
// Async future
auto async_future = std::async(std::launch::async, []{
std::this_thread::sleep_for(std::chrono::seconds(1));
return 3.1415;
});
std::cout << "Async result: " << async_future.get() << "\n";
}
LazyFuture
delays execution until get()
is calledstd::async
with std::launch::async
forces thread creation#include
#include
template<typename T>
struct Generator {
struct promise_type {
T value;
auto get_return_object() { return Generator{this}; }
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
auto yield_value(T val) {
value = val;
return std::suspend_always{};
}
};
std::coroutine_handle<promise_type> coro;
explicit Generator(promise_type* p)
: coro(std::coroutine_handle<promise_type>::from_promise(*p)) {}
~Generator() { if (coro) coro.destroy(); }
T next() {
coro.resume();
return coro.promise().value;
}
};
Generator<int> range(int start, int stop, int step=1) {
for (int i = start; i < stop; i += step) {
co_yield i;
}
}
int main() {
auto gen = range(0, 10, 2);
std::cout << "Generated: ";
for (int i = 0; i < 5; ++i) {
std::cout << gen.next() << " ";
}
}
promise_type
handles coroutine staterange
generator produces values on demand#include
#include
#include
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
Task async_work() {
struct Awaitable {
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::thread([h] {
std::cout << "Working...\n";
h.resume();
}).detach();
}
void await_resume() {}
};
co_await Awaitable{};
}
int main() {
async_work();
std::cout << "Main continues\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
Awaitable
handles thread creation#include
#include
#include
#include
std::atomic<int> counter{0};
constexpr int N = 1'000'000;
void atomic_inc() {
for (int i = 0; i < N; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}
int main() {
{
std::jthread t1(atomic_inc);
std::jthread t2(atomic_inc);
}
std::cout << "Atomic result: " << counter << " (expected " << 2*N << ")\n";
}
std::jthread
for automatic joiningAll examples require C++20 support. Compile with:
g++ -std=c++20 -fcoroutines -pthread example.cpp
Key points for each implementation:
-fcoroutines
flag in GCCQuestion 1: Coroutine Frameworks
Which of the following statements about C++20 coroutines are true?
A) co_yield
suspends a coroutine and returns a value to the caller.
B) co_return
must always return a value of type std::coroutine_handle
.
C) The promise_type
must define get_return_object()
to create the coroutine return type.
D) Coroutines require heap allocation by default.
Question 2: Generator Implementation
In the Generator
case study, which components are required for a working generator?
A) A promise_type
with yield_value()
.
B) Use of std::future
to store intermediate values.
C) A struct
inheriting std::coroutine_handle<>
.
D) Manual memory management for coroutine state.
Question 3: Python-Style List Comprehension
Which C++20 features enable Python-like list comprehension using ranges?
A) Range adaptors with |
operator chaining.
B) std::views::transform
and std::views::filter
.
C) std::execution::par_unseq
for parallel execution.
D) Coroutines to generate elements lazily.
Question 4: Lazy Futures
What are characteristics of a “lazy future” implementation?
A) Execution starts immediately upon future creation.
B) Execution is deferred until co_await
is called.
C) Requires std::async
for thread management.
D) Uses co_await
to suspend and resume execution.
Question 5: Thread Synchronization
Which synchronization primitives avoid data races in the “Fast Synchronization” case study?
A) std::mutex
with std::lock_guard
.
B) std::atomic_flag
with test_and_set()
.
C) std::counting_semaphore
.
D) std::barrier
for phased synchronization.
Question 6: std::jthread
Features
What distinguishes std::jthread
from std::thread
?
A) Automatically joins on destruction.
B) Supports cooperative interruption via request_stop()
.
C) Uses std::stop_token
to signal cancellation.
D) Guarantees lock-free atomic operations.
Question 7: Range Adaptors
Which range operations are valid for composing algorithms?
A) views::filter | views::transform
.
B) views::reverse | views::take(5)
.
C) views::concat | views::split
.
D) views::drop_while | views::join
.
Question 8: Coroutine State Management
What guarantees are true about coroutine state?
A) Coroutine state is destroyed when the coroutine completes.
B) promise_type::final_suspend()
controls whether the coroutine self-destructs.
C) The caller must manually destroy the coroutine frame.
D) std::coroutine_handle::destroy()
explicitly deallocates the coroutine state.
Question 9: std::atomic_ref
Usage
When is std::atomic_ref
necessary?
A) To enforce atomic access to non-atomic variables.
B) To replace volatile
for memory-mapped I/O.
C) To synchronize access to elements in a std::vector
.
D) To guarantee sequential consistency without explicit memory ordering.
Question 10: Cooperative Interruption
Which components participate in cooperative interruption?
A) std::stop_source
.
B) std::condition_variable_any
.
C) std::interrupt_flag
.
D) std::stop_callback
.
Question 11: std::latch
vs. std::barrier
How do std::latch
and std::barrier
differ?
A) A latch is single-use; a barrier is reusable.
B) A latch decrements a counter; a barrier waits for a fixed number of threads.
C) A barrier supports phased synchronization.
D) A latch can be incremented by multiple threads.
Question 12: Undefined Behavior in Coroutines
Which actions cause undefined behavior in coroutines?
A) Destroying a coroutine handle before the coroutine completes.
B) Resuming a coroutine after its promise object is destroyed.
C) Calling co_await
on a suspended coroutine.
D) Using co_return
without defining promise_type::return_void()
.
Question 13: std::format
Usage
Which statements about std::format
are correct?
A) Supports compile-time format string validation.
B) std::format_to
writes to an output iterator.
C) User-defined types must specialize std::formatter
.
D) std::format("{}", 3.14)
defaults to hexadecimal representation.
Question 14: Memory Order in Atomics
Which memory orders are valid for std::atomic::load()
?
A) memory_order_relaxed
.
B) memory_order_acquire
.
C) memory_order_release
.
D) memory_order_seq_cst
.
Question 15: std::span
Safety
What are risks when using std::span
?
A) Storing a span that outlives the underlying data.
B) Passing a span with static extent to a function expecting dynamic extent.
C) Modifying elements of a std::span
.
D) Using std::span
to reference non-contiguous memory.
A, C
co_yield
suspends and returns a value (A).promise_type
must define get_return_object()
©.co_return
does not require std::coroutine_handle
(B). Coroutines can avoid heap allocation via custom allocators (D: false).A, C
promise_type::yield_value()
enables co_yield
(A).std::coroutine_handle
©.std::future
or manual memory management is required (B, D: false).A, B
|
enable chaining (A).transform
and filter
compose operations (B).B, D
co_await
manages suspension (D).std::async
© are false.B, C, D
atomic_flag
(B), semaphores ©, and barriers (D) avoid data races.std::mutex
is not used in the lock-free example (A: false).A, B, C
jthread
auto-joins (A), supports request_stop()
(B), and uses stop_token
©.A, B, D
filter|transform
(A), reverse|take
(B), drop_while|join
(D).concat
and split
are not composable via |
(C: false).A, B, D
final_suspend()
controls self-destruction (B).destroy()
explicitly frees state (D). Caller does not manually destroy (C: false).A, C
atomic_ref
enforces atomic access to non-atomic variables (A).std::vector
elements ©.volatile
(B: false).A, D
stop_source
and stop_callback
handle interruption (A, D).condition_variable_any
is unrelated (B: false).A, C
B, D
return_void()
(D) cause UB.A, B, C
format_to
(B), and formatter
specialization © are correct.3.14
is decimal (D: false).A, B, D
relaxed
, acquire
, seq_cst
(A, B, D).release
is invalid for loads (C: false).A, D
span
prohibits modification (C: false).\Problem 1: Lazy Filtered Range Generator
Task: Implement a C++20 coroutine-based generator that lazily produces values from a range filtered by a predicate. The generator must:
co_yield
for value production// Test in main()
int main() {
auto gen = filtered_view(std::views::iota(1,20), [](int x){ return x%3 == 0;});
for (int val : gen | std::views::take(5)) {
std::cout << val << " "; // Should output: 3 6 9 12 15
}
}
Problem 2: async_transform Algorithm
Task: Create a parallel version of std::transform
using C++20 coroutines and std::jthread
that:
// Test in main()
int main() {
std::vector<int> src{1,2,3,4,5};
std::vector<int> dst(5);
async_transform(std::execution::par, src.begin(), src.end(), dst.begin(),
[](int x) { return x*10; });
// dst should contain {10,20,30,40,50}
}
Problem 3: Concept-Constrained Pipeline
Task: Design a type-safe pipeline operator |>
using C++20 concepts that:
// Test in main()
int main() {
auto pipeline = make_pipeline(
[](auto x) { return x*2; },
[](auto x) { return x+1; }
);
auto result = std::vector{1,2,3} |> pipeline;
// Should output {3,5,7}
}
Problem 4: Thread-Safe Observable Pattern
Task: Implement an observable pattern using C++20 synchronization primitives that:
std::atomic_ref
for thread-safe value access// Test in main()
int main() {
Observable<int> value(0);
auto observer = value.observe([](int x) {
std::cout << "Value changed to " << x << "\n";
});
std::jthread worker([&]{
for(int i=1; i<=5; ++i)
value.set(i);
});
}
Problem 5: Compile-Time String Processing
Task: Create a constexpr string processor using C++20 features that:
consteval
functions// Test in main()
int main() {
constexpr auto processed = CompileStringProcessor<"Hello_World">()
.filter([](char c){ return c != '_'; })
.transform([](char c){ return c == 'o' ? '0' : c; });
static_assert(processed.str == "Hell0World");
}
Problem 6: Cancellable Parallel Reduce
Task: Implement a parallel reduction algorithm using std::stop_token
that:
std::jthread
for worker management// Test in main()
int main() {
std::vector<int> data(1'000'000, 1);
auto [result, ok] = parallel_reduce(data, 0, std::plus<>{});
assert(result == 1'000'000 && ok);
}
Problem 7: Type-Safe Variant Visitor
Task: Design a std::variant
visitor using C++20 concepts that:
if constexpr
for type dispatch// Test in main()
int main() {
using Var = std::variant<int, std::string>;
auto visitor = make_visitor(
[](int x) { return x*2; },
[](const std::string& s) { return s.size(); }
);
Var v1 = 42, v2 = "test";
assert(visit(visitor, v1) == 84);
assert(visit(visitor, v2) == 4);
}
Problem 8: Memory-Mapped Range Adapter
Task: Create a std::span
-based range adapter that:
std::bit_cast
for type safetycontracts
// Test in main()
int main() {
std::vector<byte> buffer(1024);
auto int_view = memory_map<int>(buffer);
int_view[0] = 0x12345678;
assert(buffer[0] == 0x78 && buffer[1] == 0x56);
}
Problem 9: Constraint-Based Matrix
Task: Implement a matrix class using C++20 concepts that:
std::mdspan
for multi-dimensional access// Test in main()
int main() {
Matrix<int, 2, 3> m1{1,2,3,4,5,6};
Matrix<int, 3, 2> m2{7,8,9,10,11,12};
auto result = m1 * m2; // Should produce 2x2 matrix
assert(result[0][0] == 58);
}
Problem 10: Coroutine-Based State Machine
Task: Create a state machine using C++20 coroutines that:
co_await
for state transitions// Test in main()
int main() {
auto machine = make_state_machine(
State{"Idle", [](auto){ /*...*/ }},
State{"Active", [](auto){ /*...*/ }}
);
machine.process(Event::Start);
assert(machine.current_state() == "Active");
}
Wait for resolution…
Key Techniques:
promise_type
to track filtered valuesbegin()
/end()
for range-based for supportstd::ranges::range
constrainttake
Test Cases:
Other solutions would similarly combine C++20 features like: