C++ Project: Pipeline Lab
The Pipeline Lab is one program built from the tools of this course. A generator feeds a ranges pipeline, a constrained template totals the values, and two threads check the total.
A generator produces the numbers
The generator yields the numbers from 1 to 12. The main function collects them with a loop and prints the row.
#include <coroutine>
#include <iostream>
#include <vector>
class Generator {
public:
struct promise_type {
int current = 0;
Generator get_return_object() {
return Generator(std::coroutine_handle<promise_type>::from_promise(*this));
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
std::suspend_always yield_value(int value) {
current = value;
return {};
}
void return_void() {}
void unhandled_exception() {}
};
explicit Generator(std::coroutine_handle<promise_type> handle) {
handle_ = handle;
}
~Generator() {
if (handle_) {
handle_.destroy();
}
}
bool next() {
handle_.resume();
return !handle_.done();
}
int value() const {
return handle_.promise().current;
}
private:
std::coroutine_handle<promise_type> handle_;
};
Generator numbers(int limit) {
for (int i = 1; i <= limit; i++) {
co_yield i;
}
}
int main() {
Generator gen = numbers(12);
std::vector<int> values;
while (gen.next()) {
values.push_back(gen.value());
}
bool first = true;
for (int value : values) {
if (!first) {
std::cout << " ";
}
std::cout << value;
first = false;
}
std::cout << "\n";
return 0;
}A pipeline keeps the evens and squares them
The pipeline filters the collected values down to the even numbers. A transform squares each one, and a loop collects the results.
#include <coroutine>
#include <iostream>
#include <ranges>
#include <vector>
class Generator {
public:
struct promise_type {
int current = 0;
Generator get_return_object() {
return Generator(std::coroutine_handle<promise_type>::from_promise(*this));
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
std::suspend_always yield_value(int value) {
current = value;
return {};
}
void return_void() {}
void unhandled_exception() {}
};
explicit Generator(std::coroutine_handle<promise_type> handle) {
handle_ = handle;
}
~Generator() {
if (handle_) {
handle_.destroy();
}
}
bool next() {
handle_.resume();
return !handle_.done();
}
int value() const {
return handle_.promise().current;
}
private:
std::coroutine_handle<promise_type> handle_;
};
Generator numbers(int limit) {
for (int i = 1; i <= limit; i++) {
co_yield i;
}
}
int main() {
Generator gen = numbers(12);
std::vector<int> values;
while (gen.next()) {
values.push_back(gen.value());
}
auto pipeline = values | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return n * n; });
std::vector<int> squared;
for (int value : pipeline) {
squared.push_back(value);
}
bool first = true;
for (int value : squared) {
if (!first) {
std::cout << " ";
}
std::cout << value;
first = false;
}
std::cout << "\n";
return 0;
}A constrained template totals the values
The total function accepts only a vector of whole numbers. It adds the squared values and prints the total.
#include <concepts>
#include <coroutine>
#include <iostream>
#include <ranges>
#include <vector>
class Generator {
public:
struct promise_type {
int current = 0;
Generator get_return_object() {
return Generator(std::coroutine_handle<promise_type>::from_promise(*this));
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
std::suspend_always yield_value(int value) {
current = value;
return {};
}
void return_void() {}
void unhandled_exception() {}
};
explicit Generator(std::coroutine_handle<promise_type> handle) {
handle_ = handle;
}
~Generator() {
if (handle_) {
handle_.destroy();
}
}
bool next() {
handle_.resume();
return !handle_.done();
}
int value() const {
return handle_.promise().current;
}
private:
std::coroutine_handle<promise_type> handle_;
};
Generator numbers(int limit) {
for (int i = 1; i <= limit; i++) {
co_yield i;
}
}
template <std::integral T>
T totalAll(const std::vector<T>& values) {
T sum = 0;
for (T value : values) {
sum += value;
}
return sum;
}
int main() {
Generator gen = numbers(12);
std::vector<int> values;
while (gen.next()) {
values.push_back(gen.value());
}
auto pipeline = values | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return n * n; });
std::vector<int> squared;
for (int value : pipeline) {
squared.push_back(value);
}
std::cout << "Total: " << totalAll(squared) << "\n";
return 0;
}Example
#include <concepts>
#include <coroutine>
#include <iostream>
#include <ranges>
#include <thread>
#include <vector>
class Generator {
public:
struct promise_type {
int current = 0;
Generator get_return_object() {
return Generator(std::coroutine_handle<promise_type>::from_promise(*this));
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
std::suspend_always yield_value(int value) {
current = value;
return {};
}
void return_void() {}
void unhandled_exception() {}
};
explicit Generator(std::coroutine_handle<promise_type> handle) {
handle_ = handle;
}
~Generator() {
if (handle_) {
handle_.destroy();
}
}
bool next() {
handle_.resume();
return !handle_.done();
}
int value() const {
return handle_.promise().current;
}
private:
std::coroutine_handle<promise_type> handle_;
};
Generator numbers(int limit) {
for (int i = 1; i <= limit; i++) {
co_yield i;
}
}
template <std::integral T>
T totalAll(const std::vector<T>& values) {
T sum = 0;
for (T value : values) {
sum += value;
}
return sum;
}
int main() {
Generator gen = numbers(12);
std::vector<int> values;
while (gen.next()) {
values.push_back(gen.value());
}
auto pipeline = values | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return n * n; });
std::vector<int> squared;
for (int value : pipeline) {
squared.push_back(value);
}
bool first = true;
for (int value : squared) {
if (!first) {
std::cout << " ";
}
std::cout << value;
first = false;
}
std::cout << "\n";
std::cout << "Template total: " << totalAll(squared) << "\n";
int firstSum = 0;
int secondSum = 0;
int half = static_cast<int>(squared.size()) / 2;
std::thread firstWorker([&]() {
for (int i = 0; i < half; i++) {
firstSum += squared[i];
}
});
std::thread secondWorker([&]() {
for (int i = half; i < static_cast<int>(squared.size()); i++) {
secondSum += squared[i];
}
});
firstWorker.join();
secondWorker.join();
std::cout << "First half: " << firstSum << "\n";
std::cout << "Second half: " << secondSum << "\n";
std::cout << "Total: " << firstSum + secondSum << "\n";
return 0;
}Key points
- A generator coroutine produces the numbers from 1 to 12 one at a time.
- A ranges pipeline filters the values and transforms the ones it keeps.
- A template constrained by a concept adds a whole vector into one total.
- Each thread writes only its own slot, so the threads never share a write.
- The main thread reads the slots only after it joins both threads.
- The two thread sums agree with the template total at 364.
In this example, the generator produces the numbers from 1 to 12 and the pipeline squares the even ones. The constrained template totals the squares to 364. Two threads then sum the first and second halves into their own slots, and the main thread joins them before printing. The thread total also prints 364, so the two routes to the answer agree. Later study can add std::format, std::span or modules to a program of this shape.
Note
Every number in this lab comes from fixed data, so the outputs never change between runs.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
The first thread slot holds 56 and the second holds 308. What grand total does the program print?