June 07, 2019
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.
June 07, 2019
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.