January 25, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Christopher Wright | Christopher Wright wrote: > Andrei Alexandrescu wrote: >> Sergey Gromov wrote: >>> Sat, 24 Jan 2009 17:09:07 -0800, Andrei Alexandrescu wrote: >>> >>>> I'm working on the new range stuff and the range-based algorithm. In all likelihood, you all might be pleased with the results. >>>> >>>> I wanted to gauge opinions on a couple of issues. One is, should the empty() member function for ranges be const? On the face of it it should, but I don't want that to be a hindrance. I presume non-const empty might be necessary sometimes, e.g. figuring out if a stream is empty effectively means fetching an element off it. >>> >>> I have a hard time imagining a use for a const range. >> >> Read-only arrays for example. > > A range is essentially an iterator. It has to change its internal state to move to the next element. So a const range will not allow you to iterate over the members of a const array. It will allow you to iterate over a single element, either once or an infinite number of times. A range offering random access can give me any element without the range actually changing. > You could change ranges to have "Range next()" rather than "void next()", and that would allow you to use a const Range for reasons other than checking whether it's empty. Iterating over a range would then look like: > for (auto range = getRange(); !range.empty; range = range.next) {} > > I don't see much purpose to this. If you want polymorphic ranges, you're going to use a class for ranges. This will incur a lot of allocations, which will be dog slow. The current design would only allocate once if your range is a class. I agree. Range.next currently mutates the range in-place. Andrei | |||
January 26, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | "Andrei Alexandrescu" wrote
> I'm working on the new range stuff and the range-based algorithm. In all likelihood, you all might be pleased with the results.
>
> I wanted to gauge opinions on a couple of issues. One is, should the empty() member function for ranges be const? On the face of it it should, but I don't want that to be a hindrance. I presume non-const empty might be necessary sometimes, e.g. figuring out if a stream is empty effectively means fetching an element off it.
>
Ranges are structs. It should not matter if you want to make some const and some non-const. Basically, it depends on the range implementation. If you can make it const, make it const, if not, don't make it const. It shouldn't break any APIs.
For example, an array range might have empty be const, but a stream range might not. What matters is what functions you can use those ranges in, but those are generally templated functions, so the compiler will tell you whether it can be used or not when it tries to compile it.
Personally, I see no benefit to having empty() be const. What benefits do you gain by specifically making empty const and the other functions not const? Presumably, the underlying container must be not const in order for head, next, etc. to work properly, so there is no requirement there.
-Steve
| |||
January 26, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | Steven Schveighoffer wrote: > "Andrei Alexandrescu" wrote >> I'm working on the new range stuff and the range-based algorithm. In all likelihood, you all might be pleased with the results. >> >> I wanted to gauge opinions on a couple of issues. One is, should the empty() member function for ranges be const? On the face of it it should, but I don't want that to be a hindrance. I presume non-const empty might be necessary sometimes, e.g. figuring out if a stream is empty effectively means fetching an element off it. >> > > Ranges are structs. It should not matter if you want to make some const and some non-const. Basically, it depends on the range implementation. If you can make it const, make it const, if not, don't make it const. It shouldn't break any APIs. The problem is "higher-order" ranges - ranges that take other ranges as argument. For example, consider Retro, a range that iterates another range backwards. struct Retro(Range) { Range _input; ... bool empty() { return _input.empty; } } If Retro.empty is const and Range.empty is not, that won't compile. If Retro.empty is non-const and Range.empty is const, it will compile, but passing a const Retro won't work as well as passing a const Range. > For example, an array range might have empty be const, but a stream range might not. What matters is what functions you can use those ranges in, but those are generally templated functions, so the compiler will tell you whether it can be used or not when it tries to compile it. > > Personally, I see no benefit to having empty() be const. What benefits do you gain by specifically making empty const and the other functions not const? Presumably, the underlying container must be not const in order for head, next, etc. to work properly, so there is no requirement there. If you have a constant range with random access, empty, length, and opIndex should be enough for you to look at anything you want without altering the range itself. Andrei | |||
January 26, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | "Andrei Alexandrescu" wrote > Steven Schveighoffer wrote: >> "Andrei Alexandrescu" wrote >>> I'm working on the new range stuff and the range-based algorithm. In all likelihood, you all might be pleased with the results. >>> >>> I wanted to gauge opinions on a couple of issues. One is, should the empty() member function for ranges be const? On the face of it it should, but I don't want that to be a hindrance. I presume non-const empty might be necessary sometimes, e.g. figuring out if a stream is empty effectively means fetching an element off it. >>> >> >> Ranges are structs. It should not matter if you want to make some const and some non-const. Basically, it depends on the range implementation. If you can make it const, make it const, if not, don't make it const. It shouldn't break any APIs. > > The problem is "higher-order" ranges - ranges that take other ranges as argument. For example, consider Retro, a range that iterates another range backwards. > > struct Retro(Range) > { > Range _input; > ... > bool empty() { return _input.empty; } > } > > If Retro.empty is const and Range.empty is not, that won't compile. If Retro.empty is non-const and Range.empty is const, it will compile, but passing a const Retro won't work as well as passing a const Range. Hm... you need the template code to take the place of the designer who looks at code and decides that something should or should not be const. For the case of Retro, you'd need some sort of const detection on Range's empty function. But in general, when is it necessary for Range.empty to be const (and therefore Retro.empty to be const)? If Range is working with a const container, wouldn't you expect that Range itself would not be const, just the reference to the container is const? A range that doesn't mutate doesn't seem like a very useful idiom. >> For example, an array range might have empty be const, but a stream range might not. What matters is what functions you can use those ranges in, but those are generally templated functions, so the compiler will tell you whether it can be used or not when it tries to compile it. >> >> Personally, I see no benefit to having empty() be const. What benefits do you gain by specifically making empty const and the other functions not const? Presumably, the underlying container must be not const in order for head, next, etc. to work properly, so there is no requirement there. > > If you have a constant range with random access, empty, length, and opIndex should be enough for you to look at anything you want without altering the range itself. Why not just pass the container itself if the range isn't going to mutate? The container probably already has opIndex, and length (empty AFAIK plays no role here). If it doesn't it seems like a bad design to me. If you have a good example where this is useful, I'd like to hear it. -Steve | |||
January 26, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | Steven Schveighoffer wrote: > "Andrei Alexandrescu" wrote >> If Retro.empty is const and Range.empty is not, that won't compile. If Retro.empty is non-const and Range.empty is const, it will compile, but passing a const Retro won't work as well as passing a const Range. > > Hm... you need the template code to take the place of the designer who looks at code and decides that something should or should not be const. > > For the case of Retro, you'd need some sort of const detection on Range's empty function. Exactly. A sort of "auto-qualify". But I guess at that point some people will throw their hands in the air, and those who'd thrown their hands already will now throw their feet too. :o) > But in general, when is it necessary for Range.empty to be const (and therefore Retro.empty to be const)? If Range is working with a const container, wouldn't you expect that Range itself would not be const, just the reference to the container is const? A range that doesn't mutate doesn't seem like a very useful idiom. I agree, but then I don't want to make many assumptions about future designs. In general I'm not sure we can always assume the type of the range and the environment make it easy to obtain mutable ranges for immutable elements. Probably many people would expect to just write this: void foo(in SomeRange range) { ... } and have foo look at the range and its elements no problem, without actually iterating through the range. >>> For example, an array range might have empty be const, but a stream range might not. What matters is what functions you can use those ranges in, but those are generally templated functions, so the compiler will tell you whether it can be used or not when it tries to compile it. >>> >>> Personally, I see no benefit to having empty() be const. What benefits do you gain by specifically making empty const and the other functions not const? Presumably, the underlying container must be not const in order for head, next, etc. to work properly, so there is no requirement there. >> If you have a constant range with random access, empty, length, and opIndex should be enough for you to look at anything you want without altering the range itself. > > Why not just pass the container itself if the range isn't going to mutate? The container probably already has opIndex, and length (empty AFAIK plays no role here). If it doesn't it seems like a bad design to me. > > If you have a good example where this is useful, I'd like to hear it. I need to think that through. Andrei | |||
January 26, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | Steven Schveighoffer Wrote:
> "Andrei Alexandrescu" wrote
> > I'm working on the new range stuff and the range-based algorithm. In all likelihood, you all might be pleased with the results.
> >
> > I wanted to gauge opinions on a couple of issues. One is, should the empty() member function for ranges be const? On the face of it it should, but I don't want that to be a hindrance. I presume non-const empty might be necessary sometimes, e.g. figuring out if a stream is empty effectively means fetching an element off it.
> >
>
> Ranges are structs. It should not matter if you want to make some const and some non-const. Basically, it depends on the range implementation. If you can make it const, make it const, if not, don't make it const. It shouldn't break any APIs.
>
> For example, an array range might have empty be const, but a stream range might not. What matters is what functions you can use those ranges in, but those are generally templated functions, so the compiler will tell you whether it can be used or not when it tries to compile it.
>
> Personally, I see no benefit to having empty() be const. What benefits do you gain by specifically making empty const and the other functions not const? Presumably, the underlying container must be not const in order for head, next, etc. to work properly, so there is no requirement there.
>
> -Steve
>
>
Checking if a range is empty() prior to accessing its head is useful. If empty() is const, you can't do that.
| |||
January 26, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Denis Koroskin | On Mon, 26 Jan 2009 23:37:25 +0300, Denis Koroskin <2korden@gmail.com> wrote:
> Steven Schveighoffer Wrote:
>
>> "Andrei Alexandrescu" wrote
>> > I'm working on the new range stuff and the range-based algorithm. In
>> all
>> > likelihood, you all might be pleased with the results.
>> >
>> > I wanted to gauge opinions on a couple of issues. One is, should the
>> > empty() member function for ranges be const? On the face of it it
>> should,
>> > but I don't want that to be a hindrance. I presume non-const empty
>> might
>> > be necessary sometimes, e.g. figuring out if a stream is empty
>> effectively
>> > means fetching an element off it.
>> >
>>
>> Ranges are structs. It should not matter if you want to make some const and
>> some non-const. Basically, it depends on the range implementation. If you
>> can make it const, make it const, if not, don't make it const. It shouldn't
>> break any APIs.
>>
>> For example, an array range might have empty be const, but a stream range
>> might not. What matters is what functions you can use those ranges in, but
>> those are generally templated functions, so the compiler will tell you
>> whether it can be used or not when it tries to compile it.
>>
>> Personally, I see no benefit to having empty() be const. What benefits do
>> you gain by specifically making empty const and the other functions not
>> const? Presumably, the underlying container must be not const in order for
>> head, next, etc. to work properly, so there is no requirement there.
>>
>> -Steve
>>
>>
>
> Checking if a range is empty() prior to accessing its head is useful. If empty() is const, you can't do that.
Err.. if empty() is not const and you have a const range reference.
| |||
January 27, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | Sun, 25 Jan 2009 13:53:43 -0800, Andrei Alexandrescu wrote:
> Christopher Wright wrote:
>> Andrei Alexandrescu wrote:
>>> Sergey Gromov wrote:
>>>> Sat, 24 Jan 2009 17:09:07 -0800, Andrei Alexandrescu wrote:
>>>>
>>>>> I'm working on the new range stuff and the range-based algorithm. In all likelihood, you all might be pleased with the results.
>>>>>
>>>>> I wanted to gauge opinions on a couple of issues. One is, should the empty() member function for ranges be const? On the face of it it should, but I don't want that to be a hindrance. I presume non-const empty might be necessary sometimes, e.g. figuring out if a stream is empty effectively means fetching an element off it.
>>>>
>>>> I have a hard time imagining a use for a const range.
>>>
>>> Read-only arrays for example.
>>
>> A range is essentially an iterator. It has to change its internal state to move to the next element. So a const range will not allow you to iterate over the members of a const array. It will allow you to iterate over a single element, either once or an infinite number of times.
>
> A range offering random access can give me any element without the range actually changing.
This restricts you to algorithms working exclusively with random-access ranges. Any more generic algos won't work with const ranges. How many of those do you have?
| |||
January 27, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Denis Koroskin | "Denis Koroskin" wrote
> On Mon, 26 Jan 2009 23:37:25 +0300, Denis Koroskin <2korden@gmail.com> wrote:
>> Checking if a range is empty() prior to accessing its head is useful. If empty() is const, you can't do that.
>
> Err.. if empty() is not const and you have a const range reference.
empty not being const does not imply that you can't access a const member. Empty not being const allows all access. The question is whether a wrapping range (or any range for that matter) should implement empty as const, as an empty that IS const cannot call a non-const function of a member.
-Steve
| |||
January 27, 2009 Re: range and algorithm-related stuff | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | "Andrei Alexandrescu" wrote
> Probably many people would expect to just write this:
>
> void foo(in SomeRange range) { ... }
>
> and have foo look at the range and its elements no problem, without actually iterating through the range.
Consider that some type const Range!(T), where T is a reference that contains the data to be iterated can be copied without casting to a Range!(const T)
I think you even filed an enhancement on this, if I'm not mistaken. So a void foo(in SomeRange range) can be rewritten as void foo(ConstSomeRange), and you should technically be able to pass a const(SomeRange) into it. This is because a range is a struct, and aside from the reference to the data, everything else is stack local.
Assuming ConstSomeRange is the same as SomeRange but with the data reference being const.
-Steve
| |||
Copyright © 1999-2021 by the D Language Foundation
Permalink
Reply