Head Const

Jonathan M Davis via Digitalmars-d digitalmars-d at puremagic.com
Thu Feb 18 03:57:59 PST 2016


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- at 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


More information about the Digitalmars-d mailing list