| Thread overview | ||||||||
|---|---|---|---|---|---|---|---|---|
|
June 13, 2014 User defined forward range - foreach initializes f.r. | ||||
|---|---|---|---|---|
| ||||
Hi,
I have a template class "List" which should provide generic list functionality. In my use case, using a class instead a struct
is more comfortable.
Following unittest fails, although the forward range
implements the save method.
unittest
{
auto intList = new List!int(1,2,3);
assert(intList.length == 3);
foreach(item; intList) {}
assert(intList.length == 3);
}
class List(T)
{
private T[] items;
this(T[] items...)
{
this.items = items;
}
bool empty() const
{
return items.length == 0;
}
@property int length() const
{
return items.length;
}
void popFront()
{
items = items[1..$];
}
@property List!T save() const
{
return new List!T( cast(T[]) items);
}
T front() const
{
return opIndex(0);
}
T opIndex(size_t index) const
{
return cast(T) items[index];
}
}
I already tried different thinks, like using dup or importing std.array.
But nothing helps, the unittest fails.
Why it fails?
Kind regards
André
| ||||
June 13, 2014 Re: User defined forward range - foreach initializes f.r. | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Andre | On Fri, Jun 13, 2014 at 08:14:11PM +0200, Andre via Digitalmars-d-learn wrote: > Hi, > > I have a template class "List" which should provide generic list functionality. In my use case, using a class instead a struct is more comfortable. > > Following unittest fails, although the forward range implements the save method. [...] Generally, it's a bad idea to conflate a container with a range over its contents. In this sense, built-in arrays are a very bad example, because they show precisely this sort of conflation. But they get away with it because of their hybrid value/reference semantics. Most containers, however, don't have this sort of complex semantics, so they don't mix very well with ranges directly. It's much better to separate the container from a range over its contents. So I wouldn't put the range API methods in the List class, but implement an opSlice method that returns a struct that performs the list iteration, that implements the range API. This allows you to write: auto myList = new List(...); foreach (item; myList[]) { // N.B., myList[] calls myList.opSlice() ... } Since the range struct returned by opSlice is separate from the container itself, you don't have any risk of iteration also consuming the container. T -- Let X be the set not defined by this sentence... | |||
June 13, 2014 Re: User defined forward range - foreach initializes f.r. | ||||
|---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | Am 13.06.2014 20:22, schrieb H. S. Teoh via Digitalmars-d-learn:
> [...]
>
> Generally, it's a bad idea to conflate a container with a range over its
> contents. In this sense, built-in arrays are a very bad example, because
> they show precisely this sort of conflation. But they get away with it
> because of their hybrid value/reference semantics. Most containers,
> however, don't have this sort of complex semantics, so they don't mix
> very well with ranges directly.
>
> It's much better to separate the container from a range over its
> contents. So I wouldn't put the range API methods in the List class, but
> implement an opSlice method that returns a struct that performs the list
> iteration, that implements the range API. This allows you to write:
>
> auto myList = new List(...);
> foreach (item; myList[]) { // N.B., myList[] calls myList.opSlice()
> ...
> }
>
> Since the range struct returned by opSlice is separate from the
> container itself, you don't have any risk of iteration also consuming
> the container.
>
>
> T
>
Thank you, this information is really helpful.
Kind regards
André
| |||
June 13, 2014 Re: User defined forward range - foreach initializes f.r. | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Andre | On Friday, 13 June 2014 at 18:14:10 UTC, Andre wrote:
> Hi,
>
> I have a template class "List" which should provide generic list functionality. In my use case, using a class instead a struct
> is more comfortable.
>
> Following unittest fails, although the forward range
> implements the save method.
>
> unittest
> {
> auto intList = new List!int(1,2,3);
> assert(intList.length == 3);
> foreach(item; intList) {}
> assert(intList.length == 3);
> }
>
> ...
>
> I already tried different thinks, like using dup or importing std.array.
> But nothing helps, the unittest fails.
> Why it fails?
>
> Kind regards
> André
To add to what H.S. Teoh said, which is good advice, but doesn't quite explain why your unittest failed: Foreach takes a copy of your range, and then "consumes it". This may or may not have an effect on the original range. If you plan to use your range again after a call, you are supposed to *save* it:
unittest
{
auto intList = new List!int(1,2,3);
assert(intList.length == 3);
foreach(item; intList.save) {} //HERE
assert(intList.length == 3);
}
There.
| |||
June 13, 2014 Re: User defined forward range - foreach initializes f.r. | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Andre | I am making the following comment to have others confirm, as well as remind others about a potential problem.
On 06/13/2014 11:14 AM, Andre wrote:
> unittest
> {
> auto intList = new List!int(1,2,3);
[...]
> class List(T)
> {
> private T[] items;
>
> this(T[] items...)
> {
> this.items = items;
> }
Unrelated to your question, I think that is a bug because you are keeping a reference to a temporary array. The compiler will generate a temporary array for 1, 2, and 3 and then that array will disappear.
Am I remembering correctly?
If so, I would recommend against 'T[] items...' but use an explicit slice:
this(T[] items)
The users will slightly be inconvenienced but the program will be correct.
Or, you can continue with 'T[] items...' and then have to take a copy of the argument:
this.items = items.dup; // copied
That is obviously slower when the user actually passed in a slice. That's why I think it is better to take an explicit slice as a parameter.
Ali
| |||
June 13, 2014 Re: User defined forward range - foreach initializes f.r. | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Ali Çehreli | On Friday, 13 June 2014 at 19:03:12 UTC, Ali Çehreli wrote:
> I am making the following comment to have others confirm, as well as remind others about a potential problem.
>
> On 06/13/2014 11:14 AM, Andre wrote:
>
> > unittest
> > {
> > auto intList = new List!int(1,2,3);
>
> [...]
>
> > class List(T)
> > {
> > private T[] items;
> >
> > this(T[] items...)
> > {
> > this.items = items;
> > }
>
> Unrelated to your question, I think that is a bug because you are keeping a reference to a temporary array. The compiler will generate a temporary array for 1, 2, and 3 and then that array will disappear.
>
> Am I remembering correctly?
>
> If so, I would recommend against 'T[] items...' but use an explicit slice:
>
> this(T[] items)
>
> The users will slightly be inconvenienced but the program will be correct.
>
> Or, you can continue with 'T[] items...' and then have to take a copy of the argument:
>
> this.items = items.dup; // copied
>
> That is obviously slower when the user actually passed in a slice. That's why I think it is better to take an explicit slice as a parameter.
>
> Ali
Yes.
`fun(T)(T[]...)` is just both `fun(T...)(T t)` and `fun(T)(T[] t)` both "conveniently" packaged into one, but with twice the pitfalls.
I'd suggest avoiding it altogether. It's a non-feature (IMO).
| |||
Copyright © 1999-2021 by the D Language Foundation
Permalink
Reply