logical const without casts!

Steven Schveighoffer schveiguy at yahoo.com
Fri Sep 30 10:43:11 PDT 2011


On Fri, 30 Sep 2011 12:16:03 -0400, Christophe  
<travert at phare.normalesup.org> wrote:

> "Steven Schveighoffer" , dans le message (digitalmars.D:145821), a
>  écrit :
>> If const must be a part of the signature, then you have lost the  
>> typeless
>> aspect of the context pointer.  If we expose the const (or not const)
>> nature of the pointer, then you lose the interoperability that delegates
>> provide.  The beauty of delegates is you don't *have to* care what the
>> type of the context pointer is.  That's defined by the function the
>> delegate represents.
>
> No, you don't lose the typeless aspect of the context pointer. You lose
> a litle bit of that aspect, but not that much.
>
> In D, const is transitive. That means: everything you touch via a const
> object remains const. non-const delegate context pointer breaks that
> rule.

But transitive const by itself doesn't get you any guarantees.  It does  
not prevent you from modifying that (mutable) value via another pointer.

What it does is allow code that works with both transitive immutable and  
mutable without having to copy code.

> You have const member functions, and initially, delegates were member
> functions associated with an object, so why could not the delegate be
> const ? A const delegate would just be a delegate that promise it
> doesn't touch to its context. It's not a type-defined delegate.

A delegate for a const member function today already does not touch its  
context.  There is no need to expose the type.

> If I implement a delegate at the library level, when I call the
> function, I have to make a cast to access the context pointer. With cast
> removing implicitely the constness of the context pointer, I get the
> current D implementation of delegates. This is legal, but this is also
> undefined behavior. I believe that langage delegate work that way, that
> they are thus undefined behavior, and this should be corrected.

As long as you assume the context pointer is part of the state of the  
aggregate, then yes.  But I don't see it that way.  The context pointer is  
part of the state of the delegate, and the delegate reference itself is  
the same as a function pointer -- it's not affected by immutable or const.

> Do you see the problem in the following code:
>
> class Foo
> {
>   this(ref int _i) { i = () { return _i; } }
>   ref int delegate() dg;
>   ref int i() const { return dg(); }
> }
>
> int globalInt;
>
> immutable Foo globalFoo = Foo(globalInt);
>
> int bar(const Foo foo)
> {
>     return foo.i++;
> }
>
> int globalBar()
> {
>     return bar(globalFoo);
> }
>
>
> Answer:
>
> In multithreaded application, globalFoo, which is immutable, is
> automatically shared. However, globalInt is not. globalBar allows you to
> access to (and modify) globalInt. But globalInt is not protected for
> concurrent access. And globalInt from which thread is accessed ?

Yes, I see the problem.  It comes from immutable being shared implicitly.   
To reiterate, the fact that the data is not immutable or const is *not* an  
issue, globalInt is not immutable.  The issue is purely the sharing aspect  
of immutable.

> Possible solutions:
>
> - do nothing, and let the programmer introducing multi-threading in
> the application deal with this, even if the programmers of Foo, bar and
> buggy did not cared to document the impact of their code for
> multithreaded applications.
>
> - forbid to put/call any delegate into an immutable object: that almost
> means forbiding to put/call a delegate into a const object.
>
> - what I propose: implement the separate kind of const delegates, that
> allows to protect their context pointers, and that you can safely call
> from const/immutable data.

This also doesn't work:

class Foo
{
    this(ref int _i) { i = () { return _i; } }
    ref const(int) delegate() const dg;
    ref const(int) i() const { return dg(); }
}

int globalInt;

immutable Foo globalFoo = Foo(globalInt);

void thread1()
{
    writeln(globalFoo.i);
}

int globalBar()
{
    spawn(&thread1);
    globalInt = 5; // does thread1 see this change or not?
}

It looks like any time you call a delegate member of an immutable object,  
it could access a context pointer that is not implicitly shared.

Not to mention, what the hell does a const function mean for a delegate  
literal?  The context pointer is the context of the function, not an  
object.  How does that even work?

What is probably the "right" solution is to disallow implicit immutable  
objects which have a delegate.  This means:

a) you cannot initialize a shared or immutable such object without a  
cast.  This means the line immutable Foo globalFoo = new Foo(globalInt) is  
an error without a cast.
b) Any time you have a const object that contains a delegate, it can be  
assumed that the object is not shared.

And then we avoid dealing with the const delegate issue altogether.

>> But in the case of the bug I filed, we are talking about a
>> compiler-sanctioned implicit transformation to immutable.  We *must*
>> guarantee that when we allow an implicit transformation, that there are  
>> no
>> existing mutable copies of the data.  An explicit transformation should  
>> be
>> allowed, because then the user has accepted responsibility.
>
> With my proposal, you can very easily keep an immutable reference in a
> const delegate. The delegate will just not be callable if its
> function pointer is not const with regard to the context pointer.

You can also very easily keep a mutable reference in a const delegate  
inside an immutable object.  It's not mutable through the delegate, but  
it's also not immutable.

> In any case, if the langage decides that delegate context pointer should
> escape const protection, great care should be taken to make sure they
> don't escape purity. In my example, there should be a compiler error if
> I tried to declare Foo.i and bar pure (maybe there is already an error,
> I didn't test).
>

Yes, delegates that are pure must be typed that way (there's actually a  
bug I think where you can't explicitly name a pure delegate type).  But a  
pure attribute applies to the *function* not the context pointer.

-Steve


More information about the Digitalmars-d mailing list