On Tuesday, June 3, 2025 9:39:35 PM Mountain Daylight Time Andy Valencia via 
Digitalmars-d-learn wrote:
> On Wednesday, 4 June 2025 at 02:11:18 UTC, H. S. Teoh wrote:
> > The correct way to do this is to create a method in the
> > container that returns a range over its contents.  Popping the
> > range should NOT mutate the container.  It should be regarded
> > as something separate from the container.  Only then will you
> > get sane semantics.  Trying to conflate an unordered container
> > with a range will only lead to pain and bugs.
>
> So, concretely, my container has an opApply method, and because
> of that foreach works to iterate its contents.  Imagine that I
> now want to chain() across several of these, along the lines of:
>
> ```d
>      foreach(val; s1) {
>          ...;
>      }
>
>      // But now:
>      foreach(val; chain(s1, s2, s3)) {
>          ...;
>      }
> ```
>
> What API do I add to my container so that chain will do its
> magic?  I don't see the infrastructure under std.range.chain
> using it.  Or is there a non-Range chain I haven't found yet?

chain chains two ranges with the same element type. opApply doesn't really
have anything to do with ranges. opApply was the way to give a type the
ability to be used with foreach in D1, and it was left in for D2. You can
still use it, but in general, the preferred approach in D2 is to use ranges.

Typically, a container will implement opSlice (or opIndex, since
confusingly, both can be used in this case) and have it return a range over
the container. So, then you can do

    auto range = myContainer[];

and then iterate over the range. You can then also do

    foreach(e; myContainer[])
    {
        ...
    }

but in addition to foreach understanding the range API, it also will slice
what it's given if that type can be sliced with no arguments. So,

    foreach(e; myContainer)
    {
        ...
    }

will have the same semantics if myContainer implements opSlice with no
arguments, and its opSlice returns a range.

For the range API to work, a type needs to implement

    bool empty();
    T front();
    void popFront();

and ideally it would also implement

    typeof(this) save();

so that you can get a copy of the range and thus save the current iteration
state.

If you look at std.container, each container type follows this pattern. It
implements opSlice which then returns a range over the container.

You can then use the range over the container with range-based functions
such as chain.

- Jonathan M Davis




Reply via email to