On Thursday, 18 February 2016 at 14:58:00 UTC, ixid wrote:
On Thursday, 18 February 2016 at 11:57:59 UTC, Jonathan M Davis wrote:
The problem that folks frequently want to be able to solve that they simply cannot solve >with D's const (and headconst wouldn't help) is that they want to be able to pass an object
to a function that takes it as const or return a const object
from a const member function >and have stuff like reference counting, caching, mutexes, etc. work - stuff that has to be
part of the object (at least in pure code) but which isn't
part of its logical state and >cannot be const.

Is it not possible to have two objects, the data and the information about the data? It seems like a mistake to try to treat metadata as the data. I just ask out of interest as I lack the experience to have a meaningful view, the category confusion between data and metadata seems like a path to excessive complexity.

To some extent yes, to some extent no. Lets' say that we have a RefCounted wrapper which does reference counting, e.g.

    RefCounted!Foo foo;

The ref count is in RefCounted, not Foo, so you can do something like

    RefCounted!(const Foo) foo;

and the ref-count will still work, but if you do

    const RefCounted!Foo foo;

or

    const RefCounted!(const Foo) foo;

RefCounted can't actually mutate its ref-count anymore, even if it's copied, because the copy would have a const reference to the ref-count, because the copy was made from a const RefCounted!Foo and not a RefCounted!Foo. So, it really only helps you for one level of const. The only way to get around the problem would be if the ref-count were in a global or static variable somewhere, in which case, it can't be used in pure functions, which is definitely negative.

Similarly, what do you do with cached data? In C++, cached data would normally go in a mutable member variable so that the calculation could be saved, even if a const function called it - but since the calculation was cached and wouldn't change, the const function would still be logically const. The only way to do that in D is to either have the cache set only in mutable functions or to make the function not pure and put the data out in a global or static variable somewhere, which again, is definitely bad.

Similarly, mutexes really should be associated with the data that they're protecting, which often means being passed around with the object that contains that data. And that won't work with const. You'd once again be forced to put the mutex it a global or static variable and forego pure. synchronized works around this for some cases by having the type system put the data somewhere that's not treated as const, but it's a special case added by the language. It wouldn't work at all with explicit mutexes that didn't have special language support like that.

In C++, these sorts of problems are solved by marking the ref-count, cached variable, mutex, etc. as mutable. Then that variable can be mutated in order to do what it needs to do even though it's being used in a context where the object that it's in is const. Obviously, that can be abused by marking everything as mutable and effectively making const meaningless (which is part of why Walter isn't a big fan of C++'s const), but in practice, that doesn't normally happen, and without mutable or casting away const and mutating (neither of which are valid D), you just can't do those things with const - especially when you add pure into the mix.

- Jonathan M Davis

Reply via email to