traverse(const TreeNode& node) {
auto val = node.value;
co_yield val;
for (const auto& child : node.children)
co_yield traverse(child); // O(1) per level!
}
```
.mermaid[
graph TB
subgraph Tree["Binary Tree"]
N1["1"] --> N2["2"]
N1 --> N3["3"]
N2 --> N4["4"]
N2 --> N5["5"]
end
subgraph Output["Pre-order Traversal"]
O["1 β 2 β 4 β 5 β 3"]
end
Tree -->|"recursive yield"| Output
style N1 fill:#e74c3c,color:#fff
style N2 fill:#3498db,color:#fff
style N3 fill:#2ecc71,color:#fff
style N4 fill:#f39c12,color:#fff
style N5 fill:#9b59b6,color:#fff
]
**O(1) memory per level, O(1) overhead per yield**, regardless of tree depth. One coroutine frame active at any time.
---
### π Depth Test Results
Tested with a chain of **100 nodes**:
| Metric | `Generator` (manual loop) | `RecursiveGenerator` |
|--------|--------------------------|---------------------|
| Frames alive | 100 (simultaneous) | 1 (at a time) |
| Copies per value | O(depth) | O(1) |
| Pattern | `for (auto x : recurse()) co_yield x;` | `co_yield recurse();` |
.mermaid[
graph LR
subgraph Flat["Generator: N frames"]
F1["Frame 1"]
F2["Frame 2"]
F3["Frame 3"]
FN["... N"]
end
subgraph Recursive["RecursiveGenerator: 1 frame"]
R1["Active Frame"]
end
Flat -->|"each co_yield copies up"| Recursive
style F1 fill:#e74c3c,color:#fff
style F2 fill:#e74c3c,color:#fff
style F3 fill:#e74c3c,color:#fff
style FN fill:#e74c3c,color:#fff
style R1 fill:#2ecc71,color:#fff
]
---
class: nord-light, middle, center
## π Migration Results
---
### π digraphx-cpp Migration
Two files migrated from `cppcoro` to `py2cpp`:
| File | Change |
|------|--------|
| `include/digraphx/neg_cycle.hpp` | `cppcoro::generator` β `py::Generator` |
| `include/digraphx/neg_cycle_q.hpp` | `cppcoro::generator` β `py::Generator` |
**Build**: Switched from CMake to `xmake -y -j 10`
```
[100%]: build ok, spent 14.672s
[doctest] test cases: 38 | 38 passed | 0 failed | 0 skipped
```
All **38 tests pass**. β
---
### π ecgen-cpp Migration
Larger migration β 6 headers + 6 source files:
| Component | Old Type | New Type |
|-----------|----------|----------|
| `perm.hpp` | `cppcoro::generator` | `py::Generator` |
| `perm.hpp` | `cppcoro::generator` | `py::Generator` |
| `gray_code.hpp` | `cppcoro::recursive_generator` | `py::RecursiveGenerator` |
| `combin.hpp` | `cppcoro::recursive_generator` | `py::RecursiveGenerator` |
| `set_partition.hpp` | `cppcoro::recursive_generator` | `py::RecursiveGenerator` |
.mermaid[
graph LR
subgraph Before["Before: cppcoro"]
P1["perm.hpp"]
G1["gray_code.hpp"]
C1["combin.hpp"]
S1["set_partition.hpp"]
end
subgraph After["After: py2cpp"]
P2["perm.hpp"]
G2["gray_code.hpp"]
C2["combin.hpp"]
S2["set_partition.hpp"]
end
P1 --> P2
G1 --> G2
C1 --> C2
S1 --> S2
style Before fill:#e74c3c,color:#fff
style After fill:#2ecc71,color:#fff
]
---
### π ecgen-cpp Test Results
```
[doctest] test cases: 45 | 45 passed | 0 failed | 0 skipped
[doctest] assertions: 53 | 53 passed | 0 failed |
[doctest] Status: SUCCESS!
```
**45 tests, all passing.** β
Combined verification:
| Project | Tests | Passed |
|---------|-------|--------|
| `py2cpp` (base) | 34 | β
34 |
| `digraphx-cpp` | 38 | β
38 |
| `ecgen-cpp` | 45 | β
45 |
| **Total** | **117** | **β
117** |
Zero regressions across 3 projects. π―
---
### β¨ Before & After: Code Comparison
**Before** (cppcoro + using namespace):
```cpp
#include
#include
namespace ecgen {
using namespace cppcoro;
static auto gen0_even(int n, int k) -> recursive_generator;
}
```
**After** (py2cpp):
```cpp
#include
namespace ecgen {
static auto gen0_even(int n, int k) -> py::RecursiveGenerator;
}
```
Cleaner includes, no `using namespace`, fully qualified types. π§Ή
---
### π Summary of Changes
.mermaid[
graph TB
subgraph Removed["ποΈ Removed"]
R1["cppcoro/generator.hpp"]
R2["cppcoro/recursive_generator.hpp"]
R3["cppcoro/coroutine.hpp"]
R4["cppcoro/config.hpp"]
R5["using namespace cppcoro"]
end
subgraph Added["β Added"]
A1["py2cpp/gen.hpp"]
A2["py2cpp/recursive_gen.hpp"]
A3["py::Generator"]
A4["py::RecursiveGenerator"]
A5["../py2cpp/include in xmake"]
end
Removed --> Added
style Removed fill:#e74c3c,color:#fff
style Added fill:#2ecc71,color:#fff
]
- **~400 lines of cppcoro infrastructure** replaced by **~340 lines of py2cpp** (Generator + RecursiveGenerator)
- **Zero external dependencies** β no cppcoro library required
- **Clearer types** β fully qualified `py::Generator` in all code
- **Faster builds** β fewer headers to parse
---
class: nord-light, middle, center
## π Key Takeaways
---
### π‘ Lessons Learned
.pull-left[
**What cppcoro got wrong:**
- Over-engineered config system
- Exception_ptr in every coroutine frame
- Separated sentinel type for iteration
- Too much abstraction for a simple concept
**What we improved:**
- Minimal, focused implementation
- No exception overhead
- Clean iterator model
- Reference type support built-in
]
.pull-right[
**What stayed the same:**
- C++20 coroutine model
- `co_yield` / `co_return` syntax
- Range-based for loop iteration
- Move-only semantics
**Key design decisions:**
- Pointer storage for reference support
- `std::terminate()` for exceptions
- Public `promise_type` for coroutine integration
- Separate `RecursiveGenerator` for recursive yield
]
---
### π Complexity Comparison
$$
\begin{aligned}
\text{cppcoro} &: 187 + 35 + 177 = 399 \text{ lines} \\
\text{py2cpp} &: 97 + 236 = 333 \text{ lines} \\
\text{Savings} &: 66 \text{ lines} \;({\sim}17\%)
\end{aligned}
$$
But the real win isn't line count β it's **removing the 177-line config.hpp** and **eliminating the external dependency**.
.mermaid[
graph LR
A["399 lines\ncppcoro"] --> B["333 lines\npy2cpp"]
A -.->|"-66 lines"| B
style A fill:#e74c3c,color:#fff
style B fill:#2ecc71,color:#fff
]
**Zero external dependencies. Faster compilation. Cleaner code.**
---
### π― When to Use Which
| Need | Use |
|------|-----|
| Simple value generation | `py::Generator` |
| Reference to external container | `py::Generator` |
| Recursive tree/graph traversal | `py::RecursiveGenerator` |
| Recursive + reference | `py::RecursiveGenerator` |
| Exception recovery during iteration | **Neither** β use a different pattern |
**Bottom line**: Unless you need exception recovery mid-iteration (rare for generators), `py2cpp` is a drop-in replacement that's simpler, faster to compile, and dependency-free. π
---
count: false
class: nord-dark, middle, center
# π Thank You
## Questions? π€
### Further Reading
- Source: [github.com/luk036/py2cpp](https://github.com/luk036/py2cpp)
- Slides: [luk036.github.io/proglang/generator-remark.html](generator-remark.html)
@luk036 π¨βπ» Β· 2026 π