November 28, 2015
On Friday, 27 November 2015 at 20:14:21 UTC, Andrei Alexandrescu wrote:
> There's this oddity of built-in hash tables: a reference to a non-empty hash table can be copied and then both references refer to the same hash table object. However, if the hash table is null, copying the reference won't track the same object later on.
>
> Fast-forward to general collections. If we want to support things like reference containers, clearly that oddity must be addressed. There are two typical approaches:
>
> 1. Factory function:
>
> struct MyCollection(T)
> {
>     static MyCollection make(U...)(auto ref U args);
>     ...
> }
>
> So then client code is:
>
> auto c1 = MyCollection!(int).make(1, 2, 3);
> auto c2 = MyCollection!(int).make();
> auto c3 = c2; // refers to the same collection as c2
>
> 2. The opCall trick:
>
> struct MyCollection(T)
> {
>     static MyCollection opCall(U...)(auto ref U args);
>     ...
> }
>
> with the client code:
>
> auto c1 = MyCollection!(int)(1, 2, 3);
> auto c2 = MyCollection!(int)();
> auto c3 = c2; // refers to the same collection as c2
>
> There's some experience in various libraries with both approaches. Which would you prefer?
>
>
> Andrei

This is probably naive and silly, but: can't you just put a dummy element in the hash table on creation of the struct, set a flag "containsDummyElement" and then have all methods you implement from that struct (count, range implementation, etc) check that flag and ignore the one element it contains when the flag is set? Then when the first real element gets added, just remove the element and reset the flag. When the last element gets removed, put the dummy element back.

I feel like I'm either misunderstanding the problem or misunderstanding built-in associative arrays, so sorry if what I said above is really stupid and cannot (or should not) be done for reasons everyone else here knows about.
November 28, 2015
On Friday, 27 November 2015 at 20:14:21 UTC, Andrei Alexandrescu wrote:
> There's this oddity of built-in hash tables: a reference to a non-empty hash table can be copied and then both references refer to the same hash table object. However, if the hash table is null, copying the reference won't track the same object later on.
>
> Fast-forward to general collections. [...]
>
> Andrei

I'd prefer the factory method and we shouldn't allow lazy initialization. That's only confusing, if it sometimes works and sometimes won't work. Null container should throw.
November 28, 2015
On 11/27/2015 09:14 PM, Andrei Alexandrescu wrote:
> There's this oddity of built-in hash tables: a reference to a non-empty
> hash table can be copied and then both references refer to the same hash
> table object. However, if the hash table is null, copying the reference
> won't track the same object later on.
>
> Fast-forward to general collections. If we want to support things like
> reference containers, clearly that oddity must be addressed. There are
> two typical approaches:
>
> 1. Factory function:
>...
>
> 2. The opCall trick:
>...
>

3. (Non-internal) factory function:

auto c1 = myCollection(1,2,3);
auto c2 = myCollection!int();
auto c3 = c2; // refers to the same collection as c2
November 28, 2015
On Friday, 27 November 2015 at 20:14:21 UTC, Andrei Alexandrescu wrote:
> 1. Factory function:
> 2. The opCall trick:

1. Factory

Shouldn't opCall be used when you want something to (only) behave as a function? E.g. functors.
November 28, 2015
On 11/28/15 1:59 AM, bitwise wrote:
>
> Classes/real-ref-types dont act as you're describing, so why should
> these fake struct wrapper ref things act this way? This will likely
> achieve the exact opposite of what you're aiming for, by making
> something that's supposed to act like a reference type have different
> behaviour from D's built in ref types.

So what would work for you? -- Andrei
November 28, 2015
On 11/28/15 2:26 AM, Jakob Ovrum wrote:
> Another thing: wouldn't providing a custom allocator require a separate
> primitive?

All collections will work with IAllocator. -- Andrei
November 28, 2015
On Saturday, 28 November 2015 at 06:26:03 UTC, Jakob Ovrum wrote:
> On Friday, 27 November 2015 at 20:25:12 UTC, Adam D. Ruppe wrote:
>> That syntax is the same as constructors... if that's what you want it to look like, we ought to actually use a constructor for all but the zero-argument ones which I'd use a static named function for (perhaps .make or perhaps .makeEmpty too)
>
> While I think this would be nice and explicit, it's bad for generic code, which would have to specialize to correctly call the nullary version.

Well... doesn't work: http://dpaste.dzfl.pl/2c69cc3584b8
November 28, 2015
On Saturday, 28 November 2015 at 18:38:37 UTC, Kagamin wrote:
> Well... doesn't work: http://dpaste.dzfl.pl/2c69cc3584b8

I don't understand... of course you can't call what is returned by makeEmpty.
November 28, 2015
On Saturday, 28 November 2015 at 18:43:33 UTC, Adam D. Ruppe wrote:
> On Saturday, 28 November 2015 at 18:38:37 UTC, Kagamin wrote:
>> Well... doesn't work: http://dpaste.dzfl.pl/2c69cc3584b8
>
> I don't understand... of course you can't call what is returned by makeEmpty.

Recently someone complained that it was a mistake to use round braces for template syntax. I noticed it too, that sometimes it's hard to tell where's template instantiation and where is function invocation.
November 28, 2015
On Saturday, 28 November 2015 at 12:20:36 UTC, Timon Gehr wrote:
> 3. (Non-internal) factory function:
>
> auto c1 = myCollection(1,2,3);
> auto c2 = myCollection!int();
> auto c3 = c2; // refers to the same collection as c2

Yeah. In general, I prefer that approach. It's what we currently do with RedBlackTree. It's more flexible (e.g. it can infer the element type like we do with dynamic arrays) and less verbose. The only downside that I can think of is that it doesn't work as well in generic code that's creating a container (as in where the container type is a template argument), but that's not something that's done normally. And if the factory function is just making using templated constructors cleaner, then generic code that's constructing such a container can still use the constructors. It just wouldn't be as nice as using the factory function.

But for almost all cases, a non-internal factory function named after the container is less verbose and more flexible.

- Jonathan M Davis