On 06/08/12 19:13, Steven Schveighoffer wrote:
> On Fri, 08 Jun 2012 10:57:15 -0400, Artur Skawina <[email protected]> wrote:
> 
>> On 06/08/12 06:03, Steven Schveighoffer wrote:
> 
>>>
>>> I agree that the type should be shared(int), but the type should not 
>>> transfer to function calls or auto, it should be sticky to the particular 
>>> variable.  Only references should be sticky typed.
>>
>> The problem with this is that it should be symmetrical, IOW the conversion
>> from non-shared to shared would also have to be (implicitly) allowed.
>> A type that converts to both would be better, even if harder to implement.
> 
> It should be allowed (and is today).

Hmm. I think it shouldn't be. This is how it is today:

   shared Atomic!int ai;
   shared Atomic!(void*) ap;

   void f(Atomic!int i) {} // Atomic() struct template temporarily made 
unshared for this test.
   void fp(Atomic!(void*) i) {}

   void main() {
      f(ai);
      fp(ap);
   }

   Error: function f (Atomic!(int) i) is not callable using argument types 
(shared(Atomic!(int)))
   Error: cannot implicitly convert expression (ai) of type 
shared(Atomic!(int)) to Atomic!(int)
   Error: function fp (Atomic!(void*) i) is not callable using argument types 
(shared(Atomic!(void*)))
   Error: cannot implicitly convert expression (ap) of type 
shared(Atomic!(void*)) to Atomic!(void*)

It seems to work for built-in value types, which i didn't even realize, because 
the thought of
using them with 'shared' never crossed my mind. I don't really see why those 
should be treated
differently from user defined types, which should not allow implicit 
shared<>unshared conversions.


> I am talking about stripping head-shared, so shared(int *) automatically 
> converts to shared(int)* when used as an rvalue.

Where would the 'shared(int*) type come from? IOW, given 'shared struct S { int 
i; } S s;'
what would the type of '&s.i' be? In your model; because right now it is 
'shared(int)*'.


>>> Right, I was thinking shared structs do not make sense, since I don't think 
>>> shared members do not make sense.  Either a whole struct/class is shared or 
>>> it is not.  Because you can only put classes on the heap, shared makes 
>>> sense as an attribute for a class.
>>>
>>> But then again, it might make sense to say "this struct is only ever 
>>> shared, so it should be required to go on the heap".  I like your idea 
>>> later about identifying shared struct types that should use synchronization.
>>
>> Of course shared structs make sense, it's what allows implementing any
>> non-trivial shared type.
>>
>>    static Atomic!int counter;
>>
>> inside a function is perfectly fine. And, as somebody already mentioned
>> in this thread, omitting 'static' should cause a build failure; right
>> now it is accepted, even when written as
>>
>>    shared Atomic!int counter;
>>
>> The problem? 'shared' is silently dropped. Move the counter from a struct
>> into a function after realizing it's only accessed from one place, forget
>> to add 'static' - and the result will compile w/o even a warning.
> 
> The difference is that static is not a type constructor.

The problem is that 'shared' is lost, resulting in an incorrect program.
When you explicitly declare something as shared the compiler better treat
it as such, or fail to compile it; silently changing the meaning is never
acceptable.


> e.g.:
> 
> shared int x; // typeof(x) == int

This could be made illegal, but if it is accepted then it should retain its 
type.


> void foo(shared int *n){...}
> 
> foo(&x); // compiler error?  huh?
> 
> I think this is a no-go.  Shared has to be statically disallowed for local 
> variables.

It's a possibility. Except _static_ local variables, those must work.


>>> Yes, head stripping.  I think it should be allowed.  For instance, if you 
>>> wanted to read a shared double, and take the cosine of it, this should be 
>>> allowed:
>>>
>>> auto n = cos(sharedValue);
>>>
>>> If it's not, the alternatives are:
>>>
>>> auto n = cos(cast()sharedValue);
>>>
>>> or
>>>
>>> double v = sharedValue; // explicit removal of shared
>>> auto m = cos(v);
>>>
>>> Neither of these look necessary, I think just allowing shared value types 
>>> to automatically convert to non-shared versions works the best.
>>
>> If 'shared(VT)' implicitly converts to VT, then
>>
>>    auto myY = x.y; // typeof(myY) == shared(int)
>>
>> would still be fine.
> 
> No, because then &myY yields a reference to shared data on the stack, which 
> is what I think should be disallowed.

The only problem with shared data on the stack i can think of is portability.
But this is something that can be decided at a much later time, it wouldn't
be used much in practice anyway.

> My recommendation is that typeof(myY) == int.
> 
>> But I'm not sure allowing these implicit conversions is a good idea.
>> At least not yet. :)
> 
> Implicit conversions to and from shared already are valid.  i.e. int x = 
> sharedInt; is valid code. 

yes, but see above. shared(BVT)->BVT and shared(P*)->shared(P)* are allowed,
and i don't think the latter is necessarily sound. Yes, the current shared
model practically requires this, but i don't think raw access to shared data
is the best approach.

Note that inside synchronized() statements such conversions would be fine.
And this also reminds me - mixed shared/unshared fields inside an aggregate
should be legal. Why? Consider a mutex-like field that protects other data.
Of course, synchronized() must imply a compiler barrier for this to work,
but it needs it anyway. Memory barriers should always be explicit, btw; but
for some architectures that might not be very practical.

> I'm talking about changing the types of expressions, such that the expression 
> type is always the tail-shared version.  In fact, simply using a shared piece 
> of data as an rvalue will convert it into a tail-shared version.

Could you provide an example? Because I'm not sure what problem this is supposed
to solve. Eg. what is "a shared piece of data" and where does it come from?

artur

Reply via email to