C++ Weekly - Ep 428 - C++23's Coroutine Support: std::generator

preview_player
Показать описание
☟☟ Awesome T-Shirts! Sponsors! Books! ☟☟

CLion is a cross-platform JetBrains IDE for C and C++ with:
- A smart C and C++ editor to navigate and maintain your code base productively.
- Code analysis with quick-fixes to identify and fix bugs and style inconsistencies.
- An integrated debugger – along with other essential tools from the ecosystem – available
straight out of the box.
- And much more!

T-SHIRTS AVAILABLE!

WANT MORE JASON?

SUPPORT THE CHANNEL

GET INVOLVED

JASON'S BOOKS

► C++23 Best Practices

► C++ Best Practices

JASON'S PUZZLE BOOKS

► Object Lifetime Puzzlers Book 1

► Object Lifetime Puzzlers Book 2

► Object Lifetime Puzzlers Book 3

► Copy and Reference Puzzlers Book 1

► Copy and Reference Puzzlers Book 2

► Copy and Reference Puzzlers Book 3

► OpCode Puzzlers Book 1


RECOMMENDED BOOKS

AWESOME PROJECTS

O'Reilly VIDEOS

Рекомендации по теме
Комментарии
Автор

Very nice to see generators landing but I’m not convinced that std::exchange is contributing to the transparency or readability of this code

sjswitzer
Автор

Imagine this combined with the upcoming networking library! I don't even know what exactly is possible.

ohwow
Автор

Awesome, I hope other compilers will implement it soon as well so that people can start adapting it

darkmagic
Автор

std::generator allocates. The lambda function in the previous video returns a very weird type, since it is auto. Is there a way to have the best of both: a generator class/template, to which I tell to use a certain amount of memory to work with on the stack. kind of like the new flatmaps with arrays. e.g. std::generator<int&, int, std::array<char, 512>> ?

VolkerHantschel
Автор

I would make some observation, code like `for (auto i : foo() | bar())` was tricky in older C++ as value returned from `foo()` could be destroyed as lifetime was bound only to `foo() | bar()` expression, but recent C++ fix it and make that temps in range `for` live as long this `for` is in scope.

von_nobody
Автор

also compile with just released gcc 14.1

alexandrebustico
Автор

I know ranges are cool and offer many more functionalities (constexpr, potential random access, and so on). But it is such *much* easier to actually author generators than either traditional iterators or ranges.

LesleyLai
Автор

Going to have to get out of the habit of reaching for std::cout and use std::println instead.

Its also interesting how much code hello world with std::println produces with -O(2/3), maybe an episode on why that is the case would be interesting. At compile time it should be known I'm printing a basic string and not using any formatting and remove all the formatting boilerplate etc.

bryce.ferenczi
Автор

What are the good use cases of coroutines? Where can I learn more about them? Maybe .pdf book or something

literallynull
Автор

The difference in assembly output is quite large. I imagine the optimization maniacs have not had their fun yet with std generator

mrlumps
Автор

-Fanalyzer does not like std::generator one bit.

Also, here's a version of fib() with no UB that stops after the addition overflows:

constexpr int wrapping_add(int a, int b) noexcept {
return static_cast<unsigned>(a) + static_cast<unsigned>(b);
}

std::generator<int> fib() {
int i = 0;
int j = 1;
while (true) {
co_yield (i = std::exchange(j, wrapping_add(i, j)));
if (j < i) {
break;
}
}
}

Nobody
Автор

Or you could write a for loop and reduce the generated assembly from 338 lines of unreadable asm to 30 lines of pretty readable asm. Probably faster too, though quick-bench doesn't support GCC 14.1+ so I wasn't able to verify.

1. Copy to godbolt
2. Flip #if 1 to #if 0
3. See the assembly reduce by 90%

```
#if 1
#include <generator>
#include <iostream>
#include <utility> // std::exchange
#include <ranges>

std::generator<int> fib()
{
int i = 0;
int j = 1;

while (true)
{
co_yield i = std::exchange(j, i + j);
}
}

int main()
{
for (const auto i: fib() | std::views::take(20))
{
std::cout << i << '\n';
}
}
#else
#include <iostream>
#include <utility> // std::exchange

int main()
{
int i = 0;
int j = 1;

for (int index = 0; index < 20; ++index)
{
i = std::exchange(j, i + j);
std::cout << i << '\n';
}
}
#endif
```

colinkennedy
Автор

Yay for coroutines!
I've exploited to death `std::generator` in ways that works surprisingly through the years, it's very interesting what you can do with that as soon as you understand how to manipulate manually the iterator from `begin()`

mjKlaim
Автор

I still say that the functional paradigm seeping into C++ is a bad thing. It's ugly, harder to understand than its procedural counterpart and requires much more compiler support to optimize it than the alternative. If anyone has to ask why requiring more compiler support is a bad thing, consider that one of the chief complaints against Rust is the compilation speed and how this would make C++'s compilation speed worse than it already is and put it more on par with Rust. Consider also that the harder a language is to implement that the fewer vendors we'll have. Perhaps that doesn't matter to anyone and they're fine with only having three vendors seriously competing, but monoculture in nature is a bad thing just as it is here. You may not see the ill effects today, but transitioning in the future will be harder still than it already is. Think of all the legacy codebases out there, especially the ones that are held together with duct tape and chewing gum.

anon_y_mousse