C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Do not accept std::generator, please.

From: Barry Revzin <barry.revzin_at_[hidden]>
Date: Sat, 3 Sep 2022 12:25:55 -0500
On Sat, Sep 3, 2022 at 3:49 AM Nikl Kelbon via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> std::generator has huge problems.
> I will briefly list them:
> 1. The generator must not depend on the allocator (only the promise_type
> depends), but std::generator has a template argument for alloc, in
> particular, this prohibits adding 2 generators that allocated memory
> differently into a container. It is possible to implement this without type
> erasure.
> Link to example implementation:
>
> https://github.com/kelbon/dd_generator/blob/4053fcff456c885d9330708d2e67f103c4212319/include/dd_generator.hpp#L264
>
>
I think what you're asking is, in this example:

auto f(allocator_arg_t, CoolAllocator) -> std::generator<int>

Should CoolAllocator be type-erased (current design) or not (since it's
possible to go through coroutine_traits and have CoolAllocator be a
template parameter of the promise type and stored directly)?

Because the allocator is type-erased currently, it *is* possible to have
multiple generators in a container that use different underlying
allocators.

But note that the current design allows:

auto g() -> std::generator<int&, int, CoolAllocator>;

Which obviates the need for passing extra function parameters, if
CoolAllocator is default constructible. Having that seems useful.


> 2. Overhead for ueslss default type erasure(default behavior) and stack in
> generator (used only for element_of, which is basically for(auto&& v : gen)
> co_yield v;
> Its just uselss
> You pay for what you dont use.
>

The paper claims otherwise - I'd like to see some support for this claim.
How much overhead is there?


> 3. It is undefined behavior to call begin for std::generator twice. But
> iteraing not all values in one loop is a very logical action and expected
> behavior - just keep generating values
> Besides, such an error is extremely non-obvious when using ranges or range
> based for loop.
>

This is not at all "a very logical action and expected behavior" - far, far
from. This sort of stateful iteration is very much not the design of
ranges, and has never been. If I see this in code:

print("{}\n", v | views::take(4));
print("{}\n", v | views::take(4));

This had better either print the same (up to) four elements twice, or be
undefined behavior. It is not at all logical or expected that it should
print eight different elements. If you want to do that, you can just use
the iterators directly and keep using the same iterator through multiple
loops. This is totally fine, and would be clear to the reader what is going
on.

This is why it is undefined behavior to call begin() on *any *input range
multiple times. Because you cannot iterate such a thing multiple times, and
the way that most of these things (including generator) must be
implemented, precludes any sort of sensible behavior.


> 4. interface that is very obscure to the user and requires knowledge of
> implementation to write efficient code
> No one will write generator<const T&> foo(); without knowing why it is
> needed and why it is better for perfmornace
>

> But there are way to write generator without such problems
> https://github.com/kelbon/dd_generator
>

generator<const T&> is not a "very obscure" interface. If I want to have a
generator that yields references to const, that seems to me like a fairly
obvious thing to write. Also, in what way is it "better for performance"
and what "knowledge of implementation" is needed "to write efficient code"?
Some justification for these claims would be nice.

Barry

Received on 2022-09-03 17:26:10