www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - "Why there is no good library Head Const in D?" answered

reply Q. Schroll <qs.il.paperinik gmail.com> writes:
A simple answer: There is in std.experimental.typecons, but that 
one does not work like you'd might expect from a C++ background. 
Basically, the std.experimental.typecons.Final template imitates 
the underlying type, but disallows any assignment.
This is not even close to what C++'s const or a sophisticated 
head const would mean.

What does head_const even mean?

The idea of head const is not shallow non-assignability (that 
wouldn't give you any guarantee whatsoever), but that is is 
impossible to mutate the first layer of indirection, i.e. exactly 
the part that is copied by the postblit. If you copy a slice, 
e.g. of type int[], the pointer and length components are copied, 
but copy and original reference the same data. In a working 
head_const(int[]) farr, you can mutate the components farr[i] as 
you whish, but you cannot e.g. shorten the slice like farr = 
farr[1 .. $].

Fist things first: For class and interface types, Final does what 
you'd expect.
It is straightforward to implement head_const for pointers, 
arrays, slices, and associative arrays.
The complicated stuff is structs (not to mention unions).
"Why is it so complicated for structs?", you might ask. "Aren't 
slices laid out like structs?" Sure, but in the case of slices 
and pointers, I, the one implementing a great library head_const, 
know where indirections are and where not.

To be clear: head_const can be implemented for structs with lower 
restrictions than const, but these lower restrictions are not 
much lower. If you are fine with them, well, I'd ask you, what 
does head const gain you anyway?

What are the restrictions?

The non-static member variables are actually doable:
For every non-static member variable V v in a struct T, Final!T 
must have a property v returning a Final!V. This property cannot 
have a setter because that would alter the first layer.
Following that, POD structs can perfectly get consistent and 
useful library head_const.

The (non-static) member functions are the problem: If head_const 
were a D type qualifier, you could probably apply it to member 
functions of a struct S. If S has a member variable int[] 
mySlice, in a head_const member function, you could alter its 
elements, but not change mySlice.length for example.

Since D doesn't have head_const support, selecting which member 
functions are head_const compliant must be done manually by the 
head_const template.
When you have a struct type T and you want to select the member 
functions of head_const!T, you obviously cannot take the mutable 
ones: those could alter the member variables freely.
But only allowing const member functions is overly restrictive. 
That's the crux of the matter. There's nothing in between.
You could apply  head_const to mutable member functions, and 
head_const!T allows those, too. While that's possible, it has 
issues:
First, the author of the struct must be aware it might be used as 
a type parameter for Final.
Second, there is no way to enforce that  head_const is rightfully 
applied. The member functions could be defined anywhere, there is 
in general no way for the head_const template to inspect the body 
of a member function in question if the restrictions apply.
Thirdly, (assuming you trust the user that  head_const is 
rightfully applied) assume some  head_const member function f 
returns ref X. Does it become ref head_const!X or not?
Say the type as two member variables: X x and X[] xs. If f 
returns x, f would need to become

     ref head_const!X f(/*whatever*/);

but if it never ever returns x, it could keep the signature

     ref X f(/*whatever*/);

And as the implementer of the head_const template you cannot 
know. Again, there is no way to get the body of the function in 
general.
Again, keeping the signature may violate guarantees by 
head_const, but changing the return type of any ref returning 
functions is overly restrictive. It'd make e.g. head_const slice 
wrappers completely useless as you couldn't assign the elements 
(returned by reference by opIndex) then.

So if you say, the first issue is ugly but acceptable, still it's 
a thing like  trusted: You really need to pay attention. The 
difference to  trusted is that  head_const compliance could 
easily be checked by the compiler.

Will head_const be part of D in the future?

If you want head_const so badly, instead of writing a DIP for 
adding head_const, which will likely be rejected (from D1 to D2, 
const changed its meaning from head_const to transitive const), 
you could craft a DIP allowing for some very general way for 
user-defined attributes to check what they are applied to and 
reject that if whatever requirements aren't met (same as  safe 
rejects code, that isn't  safe by certain criteria). In case of 
head_const, it must be able to inspect the body of the member 
function (making externally defined  head_const member functions 
quite impossible) or change what the member function sees (i.e. 
the  head_const member function sees a mutable member variable of 
type T typed head_const!T.

Finally, making head_const!(immutable T) be T is easily possible, 
but immutable(head_const!T) cannot simply become immutable(T) the 
way e.g. immutable(const(T)) is immediately same as immutable(T).
To make this possible, you'd need to craft another (much smaller) 
DIP allowing something like

     alias immutable(head_const!T) = immutable(T);

in D. That way, templates like head_const could become more like 
type constructors.
Jun 07 2019
parent Q. Schroll <qs.il.paperinik gmail.com> writes:
There were some minor errors. Three times, I wrote "Final" 
meaning "head_const".

 For every non-static member variable V v in a struct T, Final!T 
 must have a property v returning a Final!V.
That should read: For every ... variable V v in a struct T, head_const!T must have a property v returning a head_const!V.
 First, the author of the struct must be aware it might be used 
 as a type parameter for Final.
That should read: ... it might be used as a type parameter for head_const. Sorry if you were confused reading over these passages.
Jun 07 2019