std.concurrency & immutable classes...

Steven Schveighoffer schveiguy at yahoo.com
Mon Feb 14 06:49:00 PST 2011


On Fri, 11 Feb 2011 17:08:26 -0500, Tomek Sowiński <just at ask.me> wrote:

> Steven Schveighoffer napisał:
>
>> > It would be much easier if he provided the specific case(s) which  
>> broke
>> > his teeth. Then we'll all know where's the problem. If it's soluble,
>> > it'll open the door to tail type modifiers in general, not just in
>> > classes. It's a burning issue e.g. with ranges (mostly struct).
>> >
>> > http://d.puremagic.com/issues/show_bug.cgi?id=5377
>> >
>> > Look at the attachment to get a feel of what hoops we'll have to jump
>> > through to side-step lack of tail X.
>>
>> I've worked through this very same problem (a few months back), thinking
>> that we need a general solution to tail-const.  The large issue with
>> tail-const for structs in the general case is that you cannot control  
>> the
>> type of 'this'.  It's always ref.  This might seem like a very
>> inconsequential detail, but I realized that a ref to X does not  
>> implicitly
>> convert to a ref to a tail-const X.  This violates a rule of two
>> indirections, in which case you are not able to implicitly convert the
>> indirect type, even if the indirect type would implicitly convert  
>> outside
>> the reference.
>>
>> A simple example, you cannot convert an int** to a const(int)**.  Reason
>> being, then you could change the indirect pointer to point to something
>> that's immutable, and the original int ** now points to immutable data.
>
> I tried to understand this on an example and now I'm even more confused.  
> :)
>
>     int* p;
>     int** pp = &p;
>     const(int)** cpp = pp;  // compiles fine
>     immutable int i = 7;
>     *cpp = &i;
>     **pp = 5;  // mutate the immutable
>     writeln(cpp, ' ', pp);
>     writeln(*cpp, ' ', *pp, ' ', &i);
>     writeln(**cpp, ' ', **pp, ' ', i);
>
> The output is interesting:
>
> 12FE08 12FE08
> 12FE14 12FE14 12FE14
> 5 5 7
>
> So even they all point to i at the end, it remains unchanged. What  
> gives? Register caching? It doesn't matter as the int** to a  
> const(int)** conversion should fail in the first place, but I'm  
> curious...

Most likely the compiler replaces usage of i as a value as a usage of the
literal 7.  Maybe change the initialization of i to something decided at
runtime and it will be more as expected.

This is more understandable if you use string literals which inherently
must be references :)

and I guess I should have said "you *shouldn't be able to* convert an
int** to a const(int)**...", because obviously that isn't enforced.  But
it should be (your example shows why).  I think there's a bugzilla on
this, anyone?

>> The same is for tail-const structs, because you go through one ref via
>> 'this' and the other ref via the referring member.
>>
>> What does this all mean?  It basically means that you have to define
>> *separate* functions for tail-const and const, and separate functions  
>> for
>> tail-immutable and immutable.  This is untenable.
>
> I, from the very first discussions, assumed tail-const functions are  
> inevitable. You define empty() as const but popFront() as tail-const.  
> Feels natural.

It seems to work for ranges, but the problem is, const instances cannot  
call tail-const functions.  This means if there is a function that makes  
sense as tail-const, you still need two implementations (the const one can  
create a tail-const copy and call the tail-const version).

Immutable is even worse, you can't call immutable functions from  
tail-immutable functions, and you can't call tail-immutable functions from  
immutable functions.

This doesn't sit right when compared to arrays, where you can call tail-*  
functions from their fully-* equivalents.  This is because they can are  
passed by value.

I don't know, maybe it's still viable, but I think we are unlikely to  
convince Andrei and Walter that they are.  If nothing else, having to have  
new const types is a definite minus.

>> You might ask "why doesn't this problem occur with tail-const arrays?",
>> well because you *don't pass them by ref*.  With structs we have no  
>> choice.
>>
>> I think what we need is a way to define two different structs as being  
>> the
>> tail-const version of the other, with some compiler help, and then we do
>> not need to define a new flavor of const functions.  We still need to
>> define these "tail-const" functions, but it comes in a more  
>> understandable
>> form.  But importantly, the implicit cast makes a *temporary* copy of  
>> the
>> struct, allowing the cast to work.
>
> I'd like to understand it better. How would you define with this scheme,  
> say, a range on a const collection, to which ranges on an (im)mutable  
> collection are implicitly convertible?

Probably with a templated range, where the template defined whether it was  
iterating const data or not.  The trick is, you need to convince the  
compiler that they implicitly convert to one another.  Either by  
assumption (which I'm not sure can be done) or explicit declaration.

-Steve


More information about the Digitalmars-d-learn mailing list