output ranges: by ref or by value?

Steven Schveighoffer schveiguy at yahoo.com
Sun Jan 3 18:42:14 PST 2010


We are arguing in circles, so I will just stop :)

I'll address the one point I think we both feel is most important below

On Sun, 03 Jan 2010 17:19:52 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail at erdani.org> wrote:

> Steven Schveighoffer wrote:
>> On Sun, 03 Jan 2010 09:25:25 -0500, Andrei Alexandrescu  
>> <SeeWebsiteForEmail at erdani.org> wrote:
>>
>>> Steven Schveighoffer wrote:
>>>
>>>> Not having opSlice be part of the interface itself does not preclude  
>>>> it from implementing opSlice, and does not preclude using ranges of  
>>>> it in std.algorithm.  If I'm not mistaken, all functions in  
>>>> std.algorithm rely on compile time interfaces.  opApply allows for  
>>>> full input range functionality for things like copying and outputting  
>>>> where templates may not be desired.
>>>
>>> The point is how much container-independent code can someone write by  
>>> using the Container interface. If all you have is a Container, you  
>>> can't use it with any range algorithm.
>>  The answer is, you don't.  Ranges via interfaces are slower than  
>> primitive functions defined on the interface, so use what's best for  
>> interfaces when you have an interface and what's best for compile-time  
>> optimization when you have the full implementation.
>
> I understand we don't agree on this. To me, making containers work with  
> algorithms is a drop-dead requirement so I will rest my case.
>
> Nevertheless, I think there's one point that got missed. It's a tad  
> subtle, and I find it pretty cool because it's the first time I used  
> covariant returns for something interesting. Consider:
>
> interface InputIter(T) { ... }
> interface ForwardIter(T) : InputIter!T { ... }
>
> class Container(T) {
>      InputIter!T opSlice() { ... }
> }
>
> class Array(T) : Container(T) {
>      class Iterator : ForwardIter!T {
> 	... all final functions ...
>      }
>      Iterator opSlice();
> }
>
> Now there are two use cases:
>
> a) The user uses Array specifically.
>
> auto a = new Array!int;
> sort(a[]);
>
> In this case, sort gets a range of type Array!(int).Iterator, which  
> defines final primitives. Therefore, the compiler does not emit ONE  
> virtual call throughout. I believe this point was lost.
>
> b) The user wants generality and OOP-style so uses Container throughout:
>
> Container!int a = new Array!int;
> auto found = find(a[], 5);
>
> This time find gets an InputIter!int, so it will use virtual calls for  
> iteration.
>
> The beauty of this design is that without any code duplication it  
> clearly spans the spectrum between static knowledge and dynamic  
> flexibility - within one design and one implementation. This is the  
> design I want to pursue.

I see why it is attractive to you.  But to me, algorithms are not the main  
driver for containers.  One thing we both agree on -- when you know the  
full implementation, algorithms from std.algorithm should be implemented  
as fast as possible.  Where we disagree is what is desired when the full  
implementation is not known.  I contend that the ability to link with  
std.algorithm isn't a requirement in that case, and is not worth  
complicating the whole world of ranges to do so (i.e. build std.algorithm  
just in case you have reference-type ranges with a "save" requirement).   
If you don't know the implementation, don't depend on std.algorithm to  
have all the answers, depend on the implementation which is abstracted  
correctly by the interface.

What this means is there will be some overlap in functions that are  
defined on the interfaces and functions defined in std.algorithm.  Most  
likely the overlapping functions both point to the same implementation  
(naturally, this should live in std.algorithm).  This is for convenience  
to people who want to use containers in the interface form, so they do not  
need to concern themselves with the abilities of std.algorithm, they just  
want containers that help them get work done.

There is still one flaw in your ideas for covariant types:  even though  
final functions can be used throughout and the possibility for inlining  
exists, you *still* need to use the heap to make copies of ranges.  That  
was and still is my biggest concern.

I think the rest of this whole post is based on our disagreement of these  
design choices, and it really doesn't seem productive to continue all the  
subtle points.

[rest of growing disagreement snipped]

BTW, I use covariant return types freely in dcollections and I agree it  
kicks ass.  It seems like black magic especially when you are returning  
possibly a class or interface which need to be handled differently.

-Steve



More information about the Digitalmars-d mailing list