On Thursday, 18 February 2016 at 10:47:57 UTC, Timon Gehr wrote:
On 18.02.2016 10:24, Walter Bright wrote:
On 2/17/2016 11:58 PM, Timon Gehr wrote:
const(headconst(T)) is the same as const(T), no?
Er, yes?
Jonathan wanted to embed a mutable reference count within a
const object. Contrary to your suggestion, headconst won't help
with that.
Exactly. 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. That's why people keep asking
for logical const. D's const simply does not work with many use
cases (especially in the more performance-driven, @system cases),
which tends to mean that you just can't use it. Generic code in
particular can't afford to use it, because it immediately shuts
out a whole set of types (just like marking a templated function
with @safe or pure is a bad idea). So, const ends up being useful
with built-in stuff like integers and arrays, but it generally
fails once user-defined types get involved.
And remember that headconst is essentially cppconst, and C++ has
to have the mutable keyword and allow casting away const and
mutating in order to solve these sorts of problems. From what I
can tell, fundamentally, there are some common use cases that
will not work with const unless it has backdoors. Mutexes and
reference counts are great examples of that. They need to be
associated with the object, but they cannot be treated as const
in order to work even if the actual data is treated as const.
Aside from C++ interoperability, headconst really doesn't seem
like it's going to solve much.
I love the fact that D treats it as undefined behavior to cast
away const and mutate. That's a huge win with regards to compiler
guarantees and being able to trust const not to mutate stuff on
its own (though other, mutable references to the same data
obviously can). And it's required for const to interact well with
immutable. But having no backdoors at all keeps popping up as a
problem - and one that headconst clearly can't solve, because it
doesn't solve it in C++. Andrei has run into problems with this
as he's being working on the new containers and RCString. What he
needed was the mutable keyword, and he was casting away const and
mutating to do it, and I had to point out to him that that wasn't
defined behavior. And if he's forgetting that, what's the lay
user doing? Some things need backdoors from const, or they simply
can't use const, and if you use those in your code, it quickly
cascades such that you're not using const much of anywhere,
because you can't. A number of folks have stated in the newsgroup
that they generally avoid const in D, because it's too
restrictive to be useful.
We can decide that we just don't care about those problems and
that you simply don't get to use const in those cases, but that
means that you lose out on all of the benefits that you get with
const preventing you from mutating objects that aren't supposed
to be mutated, and if you have to strip out const from enough of
your code base, then you lose out on its benefits entirely,
meaning that C++'s const with its backdoors would have been a win
in comparison, sad as that may be.
The more I look at it, the more I'm inclined to think that
introducing @mutable for member variables with a corresponding,
required attribute on the struct or class it's in (e.g.
@has_mutable) is really what we need to be able to solve this
problem and make D usable in some of these high performance cases
that would be using the mutable keyword in C++. It solves the
logical const problem without totally throwing away the compiler
guarantees. Any type without @has_mutable functions as it always
has, and the cases where @mutable/@has_mutable would be required
would then work with const, gaining all of its benefits for the
non-@mutable members, and it would allow the compiler to prevent
you from doing stupid stuff like mutating immutable objects,
because an object with @has_mutable couldn't be immutable.
Regardless, I really don't think that adding headconst is worth
the complication. It doesn't solve enough to be worth it. I'd
much rather see something specific to extern(C++) that's used for
mangling and which doesn't allow you to pass const objects to it
unless the C++ declaration is equivalent to transitive const (and
probably which treats return types as transitive const if they're
partially const). Remember that we ditched having headconst,
tailconst, etc. in D ages ago, because it was deemed
overcomplicated. And I don't think that that's changed.
Personally, I think that it's too complicated in C++, with
declarations like const T*, T const*, T* const, etc. Most
non-experts are going to fall flat on their face trying to
decipher C++ declarations with const in them once you start doing
much beyond const T or const T*. Let's please not go anywhere
near there with D.
- Jonathan M Davis