Speaking about D mistakes Steve spoke about missing tail const.
I was thinking about this, and I fully agree that it is a hole.
I don't know if it was already discussed, but I was thinking that one
could introduce
*const T t1;
and
*immutable T t2;
with the following meaning:
const or immutable is applied on the "dereferenced" type. For classes
this would mean the object, not the pointer.
Thus for example if T is a class type one would be able to reassign t1
t1=t2;
but not to modify the content of t1 or t2 in any way.
One can also extend it to array types: if T is U[], then it would mean
const(U)[] or immutable(U)[], and to pointer types,
*const int* would then mean const(int)*.
For other types maybe the best solution would be to
drop the const/immutable for basic types, functions and delegates
apply it to all fields of a struct (not sure how much work this would
be to implement)
This use of * should not introduce much ambiguity (a pointer is T*,
indeed also const*T would be almost as unambiguos).
One can see that this tail const is really a common type, indeed
string is such a type, and a function can be pure even if its
arguments is *immutable, because any changes would be done to a local
copy in the function.
I think that these things point toward the usefulness of a *const and
*immutable attributes.
Fawzi

Speaking about D mistakes Steve spoke about missing tail const.
I was thinking about this, and I fully agree that it is a hole.
I don't know if it was already discussed, but I was thinking that one
could introduce
*const T t1;
and
*immutable T t2;

Sorry if i am overlooking something but if we are going that far, why not
just :
const(int)* p; // tail const pointer - already here
const(int)& r; // tail const reference - will be introduced and quite
straightforward.

One can see that this tail const is really a common type, indeed string
is such a type, and a function can be pure even if its arguments is
*immutable, because any changes would be done to a local copy in the
function.
I think that these things point toward the usefulness of a *const and
*immutable attributes.

It is indeed common and IMHO it is the biggest reason why pointers are
still used too much in C++ where references should be the obvious choice.
--
Using Opera's revolutionary email client: http://www.opera.com/mail/

The real issue is not syntax but getting it into the compiler.
Apparently, there
are difficulties in implementing tail const in the compiler which made
Walter give
up on it in the past. It should be doable, but Walter is totally sick of the
issue and doesn't want to put the time in to do it - he has plenty on his plate
as it is. So, if it's going to be done, someone else has to step up to
the plate
and do it. And with the general lack of dmd developers, that hasn't
happened. No
one thus far has had both the inclination and the time.

Well... I just took a quick look at the problem from inside the
compiler. The issue is this: the compiler has a type hierarchy, and
TypeClass is one type in it. There is no separate type for a class
reference, it just uses TypeClass do designate a class reference, which
means that if your TypeClass has the const or immutable modifier, so
does your reference. So either we create a TypeClassRef to designate
the reference, or we add additional flags to TypeClass for the
reference's modifier; in either case many parts of the semantic
analysis has to be revised to take this into account.
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

The real issue is not syntax but getting it into the compiler.
Apparently, there
are difficulties in implementing tail const in the compiler which made
Walter give
up on it in the past. It should be doable, but Walter is totally sick of the
issue and doesn't want to put the time in to do it - he has plenty on his plate
as it is. So, if it's going to be done, someone else has to step up to
the plate
and do it. And with the general lack of dmd developers, that hasn't
happened. No
one thus far has had both the inclination and the time.

Well... I just took a quick look at the problem from inside the
compiler. The issue is this: the compiler has a type hierarchy, and
TypeClass is one type in it. There is no separate type for a class
reference, it just uses TypeClass do designate a class reference, which
means that if your TypeClass has the const or immutable modifier, so
does your reference. So either we create a TypeClassRef to designate
the reference, or we add additional flags to TypeClass for the
reference's modifier; in either case many parts of the semantic
analysis has to be revised to take this into account.

well as your are at it I would argue a bit more on the syntax.
[...]
I suppose that will probably considered too difficult to implement,
but I wanted to propose it again because I find that it is the most
clean solution conceptually.

It is significantly more complex, not only for the compiler but also
for the one reading/writing the code, as you'd have to propagate that
'weak_const' as a new, distinct modifier for it to be usable across
function calls. I don't think it's worth it really.
As for the syntax for classes, I feel "const(Object)ref" with the
optional ref marker is easier to grasp than introducing a new concept
called 'weak_const'. I welcome any suggestions, but my aim is to keep
the changes as small and localized as possible in the compiler and
follow as closely as possible existing language patterns.
My only concern with the "const(Object)ref" syntax is that we're
reusing 'ref' to denote an object reference with different properties
(rebindable, nullable) than what 'ref' currently stands for. But it
remains the best syntax I've seen so far.
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

My only concern with the "const(Object)ref" syntax is that we're
reusing 'ref' to denote an object reference with different properties
(rebindable, nullable) than what 'ref' currently stands for. But it
remains the best syntax I've seen so far.

Where it would be beneficial is in mimicking the tail-const properties
of arrays in generic ranges.
I have a container C, which defines a range over its elements R.
const(R) is not a usable range, because popFront cannot be const. So
now I need to define constR, which is identical to R, except the
front() function returns a const element.
So now, I need the same for immutable.
And now I need to triplicate all my functions which accept the ranges,
or return them.
And I can't use inout(R) as a return value for ranges.
If you can solve the general problem, and not just the class
tail-const, it would be hugely beneficial.
My thought was that a modifier on const itself could be stored in the
TypeInfo_Const as a boolean (tail or not), and the equivalent done in
dmd source itself.

I'm not sure I get the problem. Can you show me in code?
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

const a = map!"a+a"( [1,2,3] );
foreach ( e; a ) {
}
The foreach fails because popFront is not const. What is needed is for
typeof(a) to be Map!("a+a", const(int)[]). IOW,
is( const(Map!("a+a", int[])) == Map!("a+a", const(int)[]) ).
One possible way to do this is for all types T to have defined types
immutable_t and const_t, which by default alias to immutable(T) and
const(T), but can be defined to alias to other types. The compiler
would then automagically convert cast(const)T to cast(T.const_t)T.

Well the code asks for a constant object, and I don't see it as
reasonable for the type system to automagically infer the intent. What
should work is this:
const(int)[] data = [1,2,3];
auto a = map!"a+a"(data);
foreach (e;a) {
}
Andrei

What might be appropriate is a function tailconst( T )( T t ) that
returns a tail const version of the passed type. That is, given a
T[], const(T[]), const(T)[], immutable(T[]), or immutable(T)[], it
returns a const(T)[]. For a MyRange!R, const(MyRange!R), or
immutable(MyRange!R), it returns a MyRange!(R).tailconst_t. See bottom
of post for a (naïve) implementation.

To expound further on this, I have created the attached module.
Critique wanted.

Looks promising. A few comments.
* For TailXxx you need to handle built-in simple types (int, float...)
to return themselves. Also, structs for which hasIndirections returns
false also return themselves.
* tailconst_t does not obey Phobos' naming convention. I think it's fine
to use TailConst in spite of the apparent ambiguity.
* You may want to add more stringent checks for tailconst_t (well
TailConst etc) to make sure it's not bogus - has the same size,
compatible members etc.
Andrei

My only concern with the "const(Object)ref" syntax is that we're
reusing 'ref' to denote an object reference with different
properties (rebindable, nullable) than what 'ref' currently stands
for. But it remains the best syntax I've seen so far.

properties of arrays in generic ranges.
I have a container C, which defines a range over its elements R.
const(R) is not a usable range, because popFront cannot be const. So
now I need to define constR, which is identical to R, except the
front() function returns a const element.
So now, I need the same for immutable.
And now I need to triplicate all my functions which accept the
ranges, or return them.
And I can't use inout(R) as a return value for ranges.
If you can solve the general problem, and not just the class
tail-const, it would be hugely beneficial.
My thought was that a modifier on const itself could be stored in the
TypeInfo_Const as a boolean (tail or not), and the equivalent done in
dmd source itself.

I'm not sure I get the problem. Can you show me in code?

Here is an example range from dcollections (well, at least the pertinant
part), for a linked list:

tail inout(Range) opSlice() inout
{
...
}

I was about to post a similar analysis, but my suggested conclusion is
very different: in my humble opinion, we must make-do without tail
const. We can't afford to inflict such complexity on our users.
The burden of defining tail const/immutable/inout functions in addition
to non- tail const/immutable/inout functions (sometimes the distinction
would need to be made!) is just too high. I think we need to work out
solutions within the existing language.
Andrei

BTW, even though I conceed that my ideas are too complex to be worth
using, I don't agree we must "make-do" without tail-const. We just need
to find a different way to solve the problem. Let's talk about how we
could add some sort of custom implicit casting to the type-system. And
actually, we need implicit lvalue casting (because all member functions
have ref this).

I believe this is the level of understanding and the kind of attitude
that can push things forward.
Andrei

Here is an example range from dcollections (well, at least the
pertinant part), for a linked list:
struct Range
{
LinkNode *node;
void popFront() { node = node.next; }
}
Now, let's say I have a LinkList instance (ignore the parameterized
type). I want to pass this list to a function and ensure nothing
changes in the list. So I create a function like this:
void foo(const(LinkList) list)
{
auto r = list[]; // get a range from the list
}
Now, LinkList has a function like this:
Range opSlice()
{
Range result;
result.node = head;
return result;
}
In order to satisfy constancy, I now have to do two things. I have to
define a *different* range, because const(Range) doesn't work (popFront
is not and cannot be const). Call it ConstRange. The second thing I
have to do is now define a completely separate function for opSlice:
ConstRange opSlice() const
{
ConstRange result;
result.node = head; // result.node is tail-const
}
Now, I could possibly make Range just parameterized on the
parameterized type, but I still have to create two separate types
(whether generated by template or not).
Finally, I have to repeat *all this* for immutable. And for all
functions that take a range, or return a range.
And this is the real kicker... it *still* doesn't work correctly. Observe:
void foo(LinkList.ConstRange r)
{
}
If I have a mutable LinkList called list, I can't do foo(list[]),
because Range does not implicitly convert to ConstRange. I have to
first convert list to a const(LinkList), and then use the slice
operator.
Now, if we have tail-const that I can apply to a struct, which just
makes all references contained in the type tail-const, then this
becomes very very easy (with proposed syntax from Tomek):
tail inout(Range) opSlice() inout
{
...
}
One function, one Range defined, very simple, very elegant.
Note that I can do all this if my range is an array (as it is in
ArrayList) without modification to the compiler, because tail-const
arrays are possible.
All I want is to duplicate the implicit casting, and implicit typing,
that arrays have with tail const. If we can have a solution that
fixes the tail-const class problem *and* this problem, it will be two
birds, one stone.

A fine explanation. Thank you.
I disagree about your proposed solution, but I recognize the problem.
The basic problem with your solution is that it creates a new kind of
const, a new kind of immutable and a new kind of shared. You should
realize that for the compiler to know the constness of member variables
inside a function, it'll have to know whether the 'this' pointer is
'const' or 'tail const'. So I think it's the wrong path.
The right path would be, I think, to parametrize the constness in the
type. A way to do this within the current constrains of the language
would be to make Range implicitly convertible to ConstRange, something
you should be able to do with "alias X this", X being a function
returning a ConstRange. The disadvantages are the duplication of the
opSlice function, and the inability to cast implicitly a ref Range to
ref ConstRange or a Range[] to a ConstRange[].
I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member is
implicitly convertible to the same member of the other template.
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member is
implicitly convertible to the same member of the other template.

I had thought of that too, a long time ago, but I wasn't sure if it
could work. I'd go two steps further:
1. all the member variable names must be identical.
2. you need to identify that implicit conversion is allowed.
1 is for sanity ;)
struct Pair(T)
{
T x;
T y;
}
struct ConstPair(T)
{
const(T) y;
const(T) x;
}
2 is to ensure you are not able to incorrectly morph data into things
it should not be. i.e.:
struct Point
{
int x;
int y;
}
I don't think you should be able to implicitly cast Pair!int to Point,
sometimes you want to define different APIs for the same data.

Just like you, I don't think you should be able to implicitly cast
Pair!int to Point.
What I was suggesting is that the implicit cast would work only as long
as the struct/class instance comes from the same template definition.
That'd actually make Pair!int and Pair!uint convertible between each
other (because int and uint are implicitly converted from one another)
but ConstPair, which originate from a different template definition,
wouldn't be convertible from Pair. Instead of defining ConstPair, you'd
use Pair!(const(T)) to denote a pair of const elements, and because T
is convertible to const(T), Pair!T is also convertible to
Pair!(const(T))... as long as the memory layout is preserved, of course.
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member
is implicitly convertible to the same member of the other template.

could work. I'd go two steps further:
1. all the member variable names must be identical.
2. you need to identify that implicit conversion is allowed.
1 is for sanity ;)
struct Pair(T)
{
T x;
T y;
}
struct ConstPair(T)
{
const(T) y;
const(T) x;
}
2 is to ensure you are not able to incorrectly morph data into things
it should not be. i.e.:
struct Point
{
int x;
int y;
}
I don't think you should be able to implicitly cast Pair!int to
Point, sometimes you want to define different APIs for the same data.

Just like you, I don't think you should be able to implicitly cast
Pair!int to Point.
What I was suggesting is that the implicit cast would work only as long
as the struct/class instance comes from the same template definition.
That'd actually make Pair!int and Pair!uint convertible between each
other (because int and uint are implicitly converted from one another)
but ConstPair, which originate from a different template definition,
wouldn't be convertible from Pair. Instead of defining ConstPair, you'd
use Pair!(const(T)) to denote a pair of const elements, and because T
is convertible to const(T), Pair!T is also convertible to
Pair!(const(T))... as long as the memory layout is preserved, of course.

Oh, I misread your original idea, sorry. I like the idea.
I just think it should be explicit that the 'same memory layout' means
the names of the variables are the same. I just think of bizarre
cases where static ifs are used to confuse things.

Yes, by "same memory layout" I mean the same variables, with the same
names, occupying the same bytes.
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member is
implicitly convertible to the same member of the other template.

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member is
implicitly convertible to the same member of the other template.

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member is
implicitly convertible to the same member of the other template.

In fact, it looks like Michel's rule is very promising, just replace
"struct/class" part with "struct" in definition.

Yes, indeed. I was a little over-enthusiastic when saying it'd work for
classes too; the vtable pointer would be a problem for that. But it
seems it can work well for structs.
You're right, "A!Object b = a" should compile fine while "A!Object* b =
&a;" should not, because it'd allow you to assign any Object to the
Widget field. That said, you can still allow this convertion:
"A!(const(Object))* b = &a;". That's because the obj member becomes
const and you can no longer assign anything to it.
So, to refine the rules I'd say a templated struct can be converted to
a lvalue of another instance of the same templated struct with the same
memory layout if all members can be converted to a lvalue of their type
in the second struct. If one field can only be converted as a rvalue,
the result is a rvalue struct. We should also make it so Widget can
convert to const(Object) as an lvalue; that doesn't work currently.
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member is
implicitly convertible to the same member of the other template.

In fact, it looks like Michel's rule is very promising, just replace
"struct/class" part with "struct" in definition.

Yes, indeed. I was a little over-enthusiastic when saying it'd work for
classes too; the vtable pointer would be a problem for that. But it
seems it can work well for structs.
You're right, "A!Object b = a" should compile fine while "A!Object* b =
&a;" should not, because it'd allow you to assign any Object to the
Widget field. That said, you can still allow this convertion:
"A!(const(Object))* b = &a;". That's because the obj member becomes
const and you can no longer assign anything to it.

That wasn't very well put. Here I was looking only at the obj field. As
defined above it wouldn't compile because "fun() { obj.method() }"
won't compile for Object and the template won't instanciate. Please
ignore "fun()" when reading all this.

So, to refine the rules I'd say a templated struct can be converted to
a lvalue of another instance of the same templated struct with the same
memory layout if all members can be converted to a lvalue of their type
in the second struct. If one field can only be converted as a rvalue,
the result is a rvalue struct. We should also make it so Widget can
convert to const(Object) as an lvalue; that doesn't work currently.

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member is
implicitly convertible to the same member of the other template.

Copying a struct always bypasses the constructor, whether it's a
template or not. If you want to maintain invariants, add the
"this(this)" postblit constructor. Your invariant in the case above is
a little silly since it stores a value deduced from the template
parameter as a member variable. But if it's that important, fixing it
is trivial: add a postblit constructor.
this(this) {
static if (is(T == Object)) isObject = true;
else isObject = false;
}
I'm not sure why you imply it won't work for references. That's the
whole point of the proposal. It can work for references as long as the
memory layout is the same and each member can also be converted as a
reference.
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

Copying a struct always bypasses the constructor, whether it's a
template or not.

That's not copying, it's moving, and it always preserves type.

If you want to maintain invariants, add the
"this(this)" postblit constructor.

The postblit constructor assumes the source and target types are the same.

Your invariant in the case above is a
little silly since it stores a value deduced from the template parameter
as a member variable. But if it's that important, fixing it is trivial:
add a postblit constructor.
this(this) {
static if (is(T == Object)) isObject = true;
else isObject = false;
}

The problem is the default and implicit behavior is broken.

I'm not sure why you imply it won't work for references. That's the
whole point of the proposal. It can work for references as long as the
memory layout is the same and each member can also be converted as a
reference.

No. For references to work with mutable objects, you need equivariance,
not contravariance. It's a classic.
Andrei

Copying a struct always bypasses the constructor, whether it's a
template or not.

That's not copying, it's moving, and it always preserves type.

If you want to maintain invariants, add the
"this(this)" postblit constructor.

The postblit constructor assumes the source and target types are the same.

Your invariant in the case above is a
little silly since it stores a value deduced from the template parameter
as a member variable. But if it's that important, fixing it is trivial:
add a postblit constructor.
this(this) {
static if (is(T == Object)) isObject = true;
else isObject = false;
}

The problem is the default and implicit behavior is broken.

Well, sometime this assumption is also barrier that gets in the way.
I'll concede to you that it might not be desirable to allow this in all
cases and we might need a way to opt-in or opt-out of this.

I'm not sure why you imply it won't work for references. That's the
whole point of the proposal. It can work for references as long as the
memory layout is the same and each member can also be converted as a
reference.

No. For references to work with mutable objects, you need equivariance,
not contravariance. It's a classic.

Perhaps you should stop misinterpreting. Where did I say that
references would have to work with *mutable* objects?
struct A(T) {
T obj;
}
Now, if you have a reference to "A!Widget", it's true that you can't
convert it to a reference to "A!Object". What you could do however is
convert it to a reference to "A!(const(Object))". The compiler would
have to transitively check whether each member of the original can be
converted by reference to their new type before allowing the conversion.
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

I'm not sure why you imply it won't work for references. That's the
whole point of the proposal. It can work for references as long as the
memory layout is the same and each member can also be converted as a
reference.

No. For references to work with mutable objects, you need
equivariance, not contravariance. It's a classic.

Perhaps you should stop misinterpreting. Where did I say that
references would have to work with *mutable* objects?
struct A(T) {
T obj;
}
Now, if you have a reference to "A!Widget", it's true that you can't
convert it to a reference to "A!Object". What you could do however is
convert it to a reference to "A!(const(Object))".

The compiler would have to transitively check whether each member of
the original can be converted by reference to their new type before
allowing the conversion.

But I observe there still may be some rough edges Andrei mentioned about
this rule for a value types, so I wonder, can we make it an unsafe
library facility? Since by the end of day, all we need (better let the
compiler do it, but..) is to transitively check fields and then just
cast the damn thing.
--
Dmitry Olshansky

Copying a struct always bypasses the constructor, whether it's a
template or not.

That's not copying, it's moving, and it always preserves type.

If you want to maintain invariants, add the
"this(this)" postblit constructor.

The postblit constructor assumes the source and target types are the
same.

Your invariant in the case above is a
little silly since it stores a value deduced from the template parameter
as a member variable. But if it's that important, fixing it is trivial:
add a postblit constructor.
this(this) {
static if (is(T == Object)) isObject = true;
else isObject = false;
}

The problem is the default and implicit behavior is broken.

Well, sometime this assumption is also barrier that gets in the way.
I'll concede to you that it might not be desirable to allow this in all
cases and we might need a way to opt-in or opt-out of this.

I'm not sure why you imply it won't work for references. That's the
whole point of the proposal. It can work for references as long as the
memory layout is the same and each member can also be converted as a
reference.

No. For references to work with mutable objects, you need
equivariance, not contravariance. It's a classic.

Perhaps you should stop misinterpreting.

In fairness you changed the approach.

Where did I say that references
would have to work with *mutable* objects?
struct A(T) {
T obj;
}
Now, if you have a reference to "A!Widget", it's true that you can't
convert it to a reference to "A!Object". What you could do however is
convert it to a reference to "A!(const(Object))". The compiler would
have to transitively check whether each member of the original can be
converted by reference to their new type before allowing the conversion.

Nagonna work. Any method inside A assumes certain types, you can't
simply change all field types and just assume that methods will continue
to work as intended, even if they still compile.
I would agree such a rule works with a simple, controlled record type
such as Tuple.
Andrei

This is the nicest proposal, imo as well.
Is "ref" used here only because "C * b" would mean double indirection?

Yes. "C* b" already has the meaning of a pointer to a class reference,
so using '*' would be a breaking change. Beside that, if you use the
pointer syntax you'd expect to be able to use the "*b" syntax to
dereference the variable...
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

Speaking about D mistakes Steve spoke about missing tail const.
I was thinking about this, and I fully agree that it is a hole.
I don't know if it was already discussed, but I was thinking that one
could introduce
*const T t1;
and
*immutable T t2;

Sorry if i am overlooking something but if we are going that far, why not
just :
const(int)* p; // tail const pointer - already here
const(int)& r; // tail const reference - will be introduced and quite
straightforward.

One can see that this tail const is really a common type, indeed string
is such a type, and a function can be pure even if its arguments is
*immutable, because any changes would be done to a local copy in the
function.
I think that these things point toward the usefulness of a *const and
*immutable attributes.

It is indeed common and IMHO it is the biggest reason why pointers are
still used too much in C++ where references should be the obvious choice.

Various syntaxes have been proposed in the past. Syntax isn't really the issue.
It's pretty easy to come up with one. I think that out of the ones I've seen,
the I liked the best was the one proposed by Michel Fortin:

The real issue is not syntax but getting it into the compiler. Apparently,
there
are difficulties in implementing tail const in the compiler which made Walter
give
up on it in the past. It should be doable, but Walter is totally sick of the
issue and doesn't want to put the time in to do it - he has plenty on his plate
as it is. So, if it's going to be done, someone else has to step up to the
plate
and do it. And with the general lack of dmd developers, that hasn't happened.
No
one thus far has had both the inclination and the time.
- Jonathan M Davis

The real issue is not syntax but getting it into the compiler.
Apparently, there
are difficulties in implementing tail const in the compiler which made
Walter give
up on it in the past. It should be doable, but Walter is totally sick of
the
issue and doesn't want to put the time in to do it - he has plenty on
his plate
as it is. So, if it's going to be done, someone else has to step up to
the plate
and do it. And with the general lack of dmd developers, that hasn't
happened. No
one thus far has had both the inclination and the time.
- Jonathan M Davis

Hello community, that is my first post here.
My background is more than 10 years of C++ and to be positive I would like to
say
that there are a lot of things I love in D ( perhaps the object of a new thread
:O) ).

Speaking about D mistakes Steve spoke about missing tail const.
I was thinking about this, and I fully agree that it is a hole.

I fully agree with that and IMHO I think D must supports this natively.
At least one facet of the problem is a "syntax" problem. Doing a parallel with
C++
and concerning pointers there are 4 possible variants:
// C++
1/ int *p;
2/ int *const p;
3/ const int * p;
4/ const int *const p;
In D, if I try the summarize the situation, we have :
1/ int *p;
2/ const(int)* p;
3/ forbiden due to the "const transitivity" philosophy
4/ const(int*) p;
Now concerning D objects, there are some kind of "implicit pointers" (with
reference counting) with no direct equivalent in C++
(and hence this problem does not occur in C++).
IMHO the syntaxic problem is the consequence of a missing "place holder" for the
two "const" attributes
(because there is no more "*" to play with in the declaration).
Perhaps one idea is to make "_" plays the role of this missing place holder. To
make things clear, for a class A the D syntax would be:
1/ __ = nothing: A p ( no change )
2/ _const(A) p; (mimic "int *const p;")
3/ const_(A) p; (mimic "const int *p;" but anyway forbiden in D due to const
transitivity)
4/ const(A) p; (no change)
To summarize there would be just one keyword to add in D : "_const". This
attribute would have sense only for Objects (the same
logic would also hold for "_immutable"). To my IMHO the syntax _const is easy to
unerstand, because "_" clearly shows the place holder position and its missing
"const"
I hope this suggestion is not too naive and can help the debate...

Reading back my first post I realized that there are a lot of confusions...
please ignore it and consider the corrected version here:
At least one facet of the problem is a "syntax" problem. Doing a parallel with
C++
and concerning pointers there are 4 possible variants:
// C++
1/ int *p;
2/ const int *p;
3/ int *const p;
4/ const int *const p;
In D, if I try the summarize the situation, we have (right?) :
1/ int *p;
2/ const(int)* p;
3/ ? <- not allowed because of const transitivity property
4/ const(int*) p;
Now concerning D objects, there are some kind of "implicit pointers" (with
reference counting) with no direct equivalent in C++ (and hence this problem
does not occur in C++).
IMHO the syntaxic problem is the consequence of a missing "place holder" for
the two "const" attributes (because there is no more "*" to play with in the
declaration).
Perhaps one idea is to make "_" plays the role of this missing place holder. To
make things clear, for a class A the D syntax would be:
1/ __ = nothing: A p ( no change )
2/ const_(A) p; (mimic "const int *p;")
3/ _const(A) p; (would mimic "int *const p;" but anyway _forbiden_ in D due to
const transitivity)
4/ const(A) p; (no change)
To summarize there would be just one keyword to add in D : "const_". This
attribute would have sense only for Objects (the same logic would also hold for
"immutable_"). To my IMHO the syntax const_ is easy to unerstand, because "_"
clearly shows the place holder position and its missing "const"
(remembering C++)
... hope there is not more error in this post...sic

To summarize there would be just one keyword to add in D : "const_".
This attribute would have sense only for Objects (the same logic would
also hold for "immutable_"). To my IMHO the syntax const_ is easy to
unerstand, because "_" clearly shows the place holder position and its
missing "const"
(remembering C++)

The issue is not with syntax (I am of the impression that most who want
this, like Michel Fortin's const(A) ref a). The problem is Walter does
not want to do it, as he considers it impossible to get right. Of that
I'm not sure, but he's proven me wrong in the past, so it could happen
again.
--
Simen

Various syntaxes have been proposed in the past. Syntax isn't really the
issue. It's pretty easy to come up with one. I think that out of the
ones I've seen,
the I liked the best was the one proposed by Michel Fortin:

This is the nicest proposal, imo as well.
Is "ref" used here only because "C * b" would mean double indirection?

* has nothing to do with references. * is for pointers. We're dealing with
references here. C* would either be a pointer to a reference or a pointer to an
object (I'm not sure which, technically-speaking, since it's a bit of a pain to
deal with pointers and classes). Regardless, C* b already means something
totally different.
- Jonathan M Davis

Apparently, there
are difficulties in implementing tail const in the compiler which
made Walter give
up on it in the past. It should be doable, but Walter is totally
sick of the
issue and doesn't want to put the time in to do it - he has plenty
on his plate
as it is. So, if it's going to be done, someone else has to step
up to the plate
and do it. And with the general lack of dmd developers, that
hasn't happened. No
one thus far has had both the inclination and the time.

compiler. The issue is this: the compiler has a type hierarchy, and
TypeClass is one type in it. There is no separate type for a class
reference, it just uses TypeClass do designate a class reference,
which means that if your TypeClass has the const or immutable
modifier, so does your reference. So either we create a
TypeClassRef to designate the reference, or we add additional flags
to TypeClass for the reference's modifier; in either case many
parts of the semantic analysis has to be revised to take this into
account.

Turns out it's there's a trick that makes it much simpler than I
expected. Patch coming soon. ;-)

great!
well as your are at it I would argue a bit more on the syntax.
In my opinion it is useful more useful to have a weak_const, (or tail
const , or *const, I don't care so much about the syntax, but I care
about the concept), like I sketched in my post, and not just fix the
class issue.
Indeed as I did try to argue it is useful to have an easy way to say
"all my local stack memory might be modified, but not anything that it
refers to" (thus weak const).
This is the maximum modifiability that one can allow to arguments to
pure functions, so a very useful level of protection.
weak_const can be defined recursively:
weak_const T
is
- const(T) if T is a reference, a D has not rebinding of refs
(otherwise it should protect only the object, not the rebinding of the
ref).
- T if T is a basic type, function or delegate
- const(U)* if is(T U==U*)
- const(U)[] if is(T U==U[]) // this is a special case of the next
- WeakConst!(T) if (T==struct) where WeakConst!(T) is a structure like
T, but where all its fields are weak_const (i.e. apply recursively
weak_const to the content of the structure.
Indeed the recursion on the structure is the most complex thing, and
might be an implementation challenge, but if doable it would be very
nice.
Basically one has to set a flag for all things that are local, and
would not be affected by the weak const.
I suppose that will probably considered too difficult to implement,
but I wanted to propose it again because I find that it is the most
clean solution conceptually.
Fawzi

well as your are at it I would argue a bit more on the syntax.
[...]
I suppose that will probably considered too difficult to
implement, but I wanted to propose it again because I find that it
is the most clean solution conceptually.

It is significantly more complex, not only for the compiler but also
for the one reading/writing the code, as you'd have to propagate
that 'weak_const' as a new, distinct modifier for it to be usable
across function calls. I don't think it's worth it really.

ok, eheh I just realized that also the tail shared protection has
exactly the same constraints as the weak const (or tail const), and
also for that it seems that the more complex struct case was scrapped,
restricting it to pointer array and refs.

As for the syntax for classes, I feel "const(Object)ref" with the
optional ref marker is easier to grasp than introducing a new
concept called 'weak_const'. I welcome any suggestions, but my aim
is to keep the changes as small and localized as possible in the
compiler and follow as closely as possible existing language patterns.
My only concern with the "const(Object)ref" syntax is that we're
reusing 'ref' to denote an object reference with different
properties (rebindable, nullable) than what 'ref' currently stands
for. But it remains the best syntax I've seen so far.
--
Michel Fortin
michel.fortin michelf.com
http://michelf.com/

well as your are at it I would argue a bit more on the syntax.
[...]
I suppose that will probably considered too difficult to implement,
but I wanted to propose it again because I find that it is the most
clean solution conceptually.

It is significantly more complex, not only for the compiler but also for
the one reading/writing the code, as you'd have to propagate that
'weak_const' as a new, distinct modifier for it to be usable across
function calls. I don't think it's worth it really.
As for the syntax for classes, I feel "const(Object)ref" with the
optional ref marker is easier to grasp than introducing a new concept
called 'weak_const'. I welcome any suggestions, but my aim is to keep
the changes as small and localized as possible in the compiler and
follow as closely as possible existing language patterns.
My only concern with the "const(Object)ref" syntax is that we're reusing
'ref' to denote an object reference with different properties
(rebindable, nullable) than what 'ref' currently stands for. But it
remains the best syntax I've seen so far.

Where it would be beneficial is in mimicking the tail-const properties of
arrays in generic ranges.
I have a container C, which defines a range over its elements R.
const(R) is not a usable range, because popFront cannot be const. So now
I need to define constR, which is identical to R, except the front()
function returns a const element.
So now, I need the same for immutable.
And now I need to triplicate all my functions which accept the ranges, or
return them.
And I can't use inout(R) as a return value for ranges.
If you can solve the general problem, and not just the class tail-const,
it would be hugely beneficial.
My thought was that a modifier on const itself could be stored in the
TypeInfo_Const as a boolean (tail or not), and the equivalent done in dmd
source itself.
-Steve

const a = map!"a+a"( [1,2,3] );
foreach ( e; a ) {
}
The foreach fails because popFront is not const. What is needed is for
typeof(a) to be Map!("a+a", const(int)[]). IOW,
is( const(Map!("a+a", int[])) == Map!("a+a", const(int)[]) ).
One possible way to do this is for all types T to have defined types
immutable_t and const_t, which by default alias to immutable(T) and
const(T), but can be defined to alias to other types. The compiler
would then automagically convert cast(const)T to cast(T.const_t)T.
--
Simen

const a =3D map!"a+a"( [1,2,3] );
foreach ( e; a ) {
}
The foreach fails because popFront is not const. What is needed is fo=

typeof(a) to be Map!("a+a", const(int)[]). IOW,
is( const(Map!("a+a", int[])) =3D=3D Map!("a+a", const(int)[]) ).
One possible way to do this is for all types T to have defined types
immutable_t and const_t, which by default alias to immutable(T) and
const(T), but can be defined to alias to other types. The compiler
would then automagically convert cast(const)T to cast(T.const_t)T.

Well the code asks for a constant object, and I don't see it as =

reasonable for the type system to automagically infer the intent.

True. I believe I was thinking that T should be implicitly convertible
to T.const_t (or tailconst_t, as may be more appropriate).
What might be appropriate is a function tailconst( T )( T t ) that
returns a tail const version of the passed type. That is, given a
T[], const(T[]), const(T)[], immutable(T[]), or immutable(T)[], it
returns a const(T)[]. For a MyRange!R, const(MyRange!R), or
immutable(MyRange!R), it returns a MyRange!(R).tailconst_t. See bottom
of post for a (na=C3=AFve) implementation.

My only concern with the "const(Object)ref" syntax is that we're
reusing 'ref' to denote an object reference with different
properties (rebindable, nullable) than what 'ref' currently stands
for. But it remains the best syntax I've seen so far.

of arrays in generic ranges.
I have a container C, which defines a range over its elements R.
const(R) is not a usable range, because popFront cannot be const. So
now I need to define constR, which is identical to R, except the
front() function returns a const element.
So now, I need the same for immutable.
And now I need to triplicate all my functions which accept the ranges,
or return them.
And I can't use inout(R) as a return value for ranges.
If you can solve the general problem, and not just the class
tail-const, it would be hugely beneficial.
My thought was that a modifier on const itself could be stored in the
TypeInfo_Const as a boolean (tail or not), and the equivalent done in
dmd source itself.

I'm not sure I get the problem. Can you show me in code?

Here is an example range from dcollections (well, at least the pertinant
part), for a linked list:
struct Range
{
LinkNode *node;
void popFront() { node = node.next; }
}
Now, let's say I have a LinkList instance (ignore the parameterized
type). I want to pass this list to a function and ensure nothing changes
in the list. So I create a function like this:
void foo(const(LinkList) list)
{
auto r = list[]; // get a range from the list
}
Now, LinkList has a function like this:
Range opSlice()
{
Range result;
result.node = head;
return result;
}
In order to satisfy constancy, I now have to do two things. I have to
define a *different* range, because const(Range) doesn't work (popFront is
not and cannot be const). Call it ConstRange. The second thing I have to
do is now define a completely separate function for opSlice:
ConstRange opSlice() const
{
ConstRange result;
result.node = head; // result.node is tail-const
}
Now, I could possibly make Range just parameterized on the parameterized
type, but I still have to create two separate types (whether generated by
template or not).
Finally, I have to repeat *all this* for immutable. And for all functions
that take a range, or return a range.
And this is the real kicker... it *still* doesn't work correctly. Observe:
void foo(LinkList.ConstRange r)
{
}
If I have a mutable LinkList called list, I can't do foo(list[]), because
Range does not implicitly convert to ConstRange. I have to first convert
list to a const(LinkList), and then use the slice operator.
Now, if we have tail-const that I can apply to a struct, which just makes
all references contained in the type tail-const, then this becomes very
very easy (with proposed syntax from Tomek):
tail inout(Range) opSlice() inout
{
...
}
One function, one Range defined, very simple, very elegant.
Note that I can do all this if my range is an array (as it is in
ArrayList) without modification to the compiler, because tail-const arrays
are possible.
All I want is to duplicate the implicit casting, and implicit typing, that
arrays have with tail const. If we can have a solution that fixes the
tail-const class problem *and* this problem, it will be two birds, one
stone.
-Steve

const a = map!"a+a"( [1,2,3] );
foreach ( e; a ) {
}
The foreach fails because popFront is not const. What is needed is for
typeof(a) to be Map!("a+a", const(int)[]). IOW,
is( const(Map!("a+a", int[])) == Map!("a+a", const(int)[]) ).
One possible way to do this is for all types T to have defined types
immutable_t and const_t, which by default alias to immutable(T) and
const(T), but can be defined to alias to other types. The compiler
would then automagically convert cast(const)T to cast(T.const_t)T.

Well the code asks for a constant object, and I don't see it as
reasonable for the type system to automagically infer the intent. What
should work is this:
const(int)[] data = [1,2,3];
auto a = map!"a+a"(data);
foreach (e;a) {
}

Now, change data to an SList range. This is what I'm talking about.
Arrays already enjoy the benefits of tail-const, I want to extend that to
all range types (and in fact all struct types).
-Steve

My only concern with the "const(Object)ref" syntax is that we're
reusing 'ref' to denote an object reference with different
properties (rebindable, nullable) than what 'ref' currently stands
for. But it remains the best syntax I've seen so far.

properties of arrays in generic ranges.
I have a container C, which defines a range over its elements R.
const(R) is not a usable range, because popFront cannot be const. So
now I need to define constR, which is identical to R, except the
front() function returns a const element.
So now, I need the same for immutable.
And now I need to triplicate all my functions which accept the
ranges, or return them.
And I can't use inout(R) as a return value for ranges.
If you can solve the general problem, and not just the class
tail-const, it would be hugely beneficial.
My thought was that a modifier on const itself could be stored in the
TypeInfo_Const as a boolean (tail or not), and the equivalent done in
dmd source itself.

I'm not sure I get the problem. Can you show me in code?

Here is an example range from dcollections (well, at least the pertinant
part), for a linked list:

tail inout(Range) opSlice() inout
{
...
}

I was about to post a similar analysis, but my suggested conclusion is
very different: in my humble opinion, we must make-do without tail
const. We can't afford to inflict such complexity on our users.
The burden of defining tail const/immutable/inout functions in addition
to non- tail const/immutable/inout functions (sometimes the distinction
would need to be made!) is just too high. I think we need to work out
solutions within the existing language.

I had not thought of it this way. I assumed you would only need to define
a function as tail-const or const. Certainly if a function can be const,
you don't need a tail-const version also do you? A ref to tail-const
should implicitly cast to ref to const. If you need tail-const version,
then a const object should not be able to call this.
Immutable is different, ref to tail-immutable does not implicitly cast to
ref immutable. This really is a shame, I think you are right, we cannot
do tail-const generically in this way :( I overlooked this because arrays
are passed to their 'member' functions generally by value and not by
reference. The same is not true for structs with member functions.
But we absolutely need a way to say "this is implicitly castable to it's
tail-const version." This is the problem I showed at the end of my
earlier post -- you cannot implicitly convert Range into ConstRange. It
would be nice to have an enforceable way to allow this, so that the burden
of proof that implicit casting works properly is not on the developer.
In addition, the way inout works would be nice to hook into this
mechanism, so you could describe how inout applies to a tail-inout version.
Thanks for pointing this out.
-Steve

My only concern with the "const(Object)ref" syntax is that we're
reusing 'ref' to denote an object reference with different
properties (rebindable, nullable) than what 'ref' currently stands
for. But it remains the best syntax I've seen so far.

properties of arrays in generic ranges.
I have a container C, which defines a range over its elements R.
const(R) is not a usable range, because popFront cannot be const. So
now I need to define constR, which is identical to R, except the
front() function returns a const element.
So now, I need the same for immutable.
And now I need to triplicate all my functions which accept the
ranges, or return them.
And I can't use inout(R) as a return value for ranges.
If you can solve the general problem, and not just the class
tail-const, it would be hugely beneficial.
My thought was that a modifier on const itself could be stored in the
TypeInfo_Const as a boolean (tail or not), and the equivalent done in
dmd source itself.

I'm not sure I get the problem. Can you show me in code?

Here is an example range from dcollections (well, at least the pertinant
part), for a linked list:

tail inout(Range) opSlice() inout
{
...
}

I was about to post a similar analysis, but my suggested conclusion is
very different: in my humble opinion, we must make-do without tail
const. We can't afford to inflict such complexity on our users.

BTW, even though I conceed that my ideas are too complex to be worth
using, I don't agree we must "make-do" without tail-const. We just need
to find a different way to solve the problem. Let's talk about how we
could add some sort of custom implicit casting to the type-system. And
actually, we need implicit lvalue casting (because all member functions
have ref this).
-Steve

A fine explanation. Thank you.
I disagree about your proposed solution, but I recognize the problem.
The basic problem with your solution is that it creates a new kind of
const, a new kind of immutable and a new kind of shared. You should
realize that for the compiler to know the constness of member variables
inside a function, it'll have to know whether the 'this' pointer is
'const' or 'tail const'. So I think it's the wrong path.

Yes, Andrei also pointed out this problem. Part of the issue is that
member functions are always passed 'this' by ref. Arrays don't have this
problem because you have control over passing them by ref or by value. I
agree my solution does not work, or actually that it works but makes
things worse :)

The right path would be, I think, to parametrize the constness in the
type. A way to do this within the current constrains of the language
would be to make Range implicitly convertible to ConstRange, something
you should be able to do with "alias X this", X being a function
returning a ConstRange. The disadvantages are the duplication of the
opSlice function, and the inability to cast implicitly a ref Range to
ref ConstRange or a Range[] to a ConstRange[].

In fact, I'm not sure you could do Range[] to ConstRange[], I think that's
two levels of indirection.
I can sort of live with defining multiple functions, at least it would be
possible to make something work.
If we could have some way to hook inout, like designate Range for mutable,
ConstRange for const, and ImmutableRange for immutable, and let InoutRange
represent the inout type (which gets implicitly converted to one of those )
But maybe I'm dreaming :)

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member is
implicitly convertible to the same member of the other template.

I had thought of that too, a long time ago, but I wasn't sure if it could
work. I'd go two steps further:
1. all the member variable names must be identical.
2. you need to identify that implicit conversion is allowed.
1 is for sanity ;)
struct Pair(T)
{
T x;
T y;
}
struct ConstPair(T)
{
const(T) y;
const(T) x;
}
2 is to ensure you are not able to incorrectly morph data into things it
should not be. i.e.:
struct Point
{
int x;
int y;
}
I don't think you should be able to implicitly cast Pair!int to Point,
sometimes you want to define different APIs for the same data.
-Steve

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member
is implicitly convertible to the same member of the other template.

could work. I'd go two steps further:
1. all the member variable names must be identical.
2. you need to identify that implicit conversion is allowed.
1 is for sanity ;)
struct Pair(T)
{
T x;
T y;
}
struct ConstPair(T)
{
const(T) y;
const(T) x;
}
2 is to ensure you are not able to incorrectly morph data into things
it should not be. i.e.:
struct Point
{
int x;
int y;
}
I don't think you should be able to implicitly cast Pair!int to
Point, sometimes you want to define different APIs for the same data.

Just like you, I don't think you should be able to implicitly cast
Pair!int to Point.
What I was suggesting is that the implicit cast would work only as long
as the struct/class instance comes from the same template definition.
That'd actually make Pair!int and Pair!uint convertible between each
other (because int and uint are implicitly converted from one another)
but ConstPair, which originate from a different template definition,
wouldn't be convertible from Pair. Instead of defining ConstPair, you'd
use Pair!(const(T)) to denote a pair of const elements, and because T is
convertible to const(T), Pair!T is also convertible to
Pair!(const(T))... as long as the memory layout is preserved, of course.

Oh, I misread your original idea, sorry. I like the idea.
I just think it should be explicit that the 'same memory layout' means the
names of the variables are the same. I just think of bizarre cases where
static ifs are used to confuse things.
-Steve

What might be appropriate is a function tailconst( T )( T t ) that
returns a tail const version of the passed type. That is, given a
T[], const(T[]), const(T)[], immutable(T[]), or immutable(T)[], it
returns a const(T)[]. For a MyRange!R, const(MyRange!R), or
immutable(MyRange!R), it returns a MyRange!(R).tailconst_t. See bottom=

My only concern with the "const(Object)ref" syntax is that we're
reusing 'ref' to denote an object reference with different
properties (rebindable, nullable) than what 'ref' currently
stands
for. But it remains the best syntax I've seen so far.

properties of arrays in generic ranges.
I have a container C, which defines a range over its elements R.
const(R) is not a usable range, because popFront cannot be
const. So
now I need to define constR, which is identical to R, except the
front() function returns a const element.
So now, I need the same for immutable.
And now I need to triplicate all my functions which accept the
ranges, or return them.
And I can't use inout(R) as a return value for ranges.
If you can solve the general problem, and not just the class
tail-const, it would be hugely beneficial.
My thought was that a modifier on const itself could be stored
in the
TypeInfo_Const as a boolean (tail or not), and the equivalent
done in
dmd source itself.

I'm not sure I get the problem. Can you show me in code?

Here is an example range from dcollections (well, at least the
pertinant
part), for a linked list:

tail inout(Range) opSlice() inout
{
...
}

I was about to post a similar analysis, but my suggested conclusion
is very different: in my humble opinion, we must make-do without
tail const. We can't afford to inflict such complexity on our users.

BTW, even though I conceed that my ideas are too complex to be worth
using, I don't agree we must "make-do" without tail-const. We just
need to find a different way to solve the problem. Let's talk about
how we could add some sort of custom implicit casting to the type-
system. And actually, we need implicit lvalue casting (because all
member functions have ref this).

I fully agree with this.
I will try to recap what I think is the most clean solution from the
conceptual point of view, then maybe others have an idea on how to
find a good solution that is not too difficult to implement, and
doesn't break what was said in TDPL too much.
The current const implies that the references to that type have to be
constant.
tail const, and has the recursive definition I had given:
valueConst(T) is
T for basic types functions and templates
refConst(U)* if is(T U==U*)
refConst(U)[] if is(T U==U[])
V if is(T == struct), where V is a structure just like T, but where
each of its fields is tail const
tail const marks all that is copied when one assigns T v2=v1; as
mutable.
Indeed to protect v1 it is not needed to protect the values that get
copied in the assignment, those values can be changed without changing
v1.
For this reason tail const is the most weak const that one can have in
pure functions, in ideally should mean tail const (thus in some way
tail const comes from the protection of a starting const.
any lvalue by default should be tail const, if it was const, and tail
immutable if it was immutable, but is implicitly convertible to full
const/immutable.
in a way tail const is more fundamental as it is the least protection
that one has to give to protect some data owned by others.
If I have a global variable the current const/immutable can guarantee
that its value will not change, while tail immutable guarantees that
one can safely point to that data as it won't be changed: that data
can be shared safely (not necessarily by several threads, even simply
by several objects).
Thus both have their function, but in general I think that tail const
might be even more important (if I had to choose one const/immutable
type I would choose the tail one).

I have an idea that would fix those: make a template struct/class
instance implicitly convertible to another instance of that same
template if all members share the same memory layout and each member
is
implicitly convertible to the same member of the other template.

These are the kinds of things I am afraid of with static if. Because we
can change the behavior it is not safe to assume that we can simply copy
or do a reinterpret-cast. I like the idea, but I think we need some sort
of way to limit the scope of this feature. Can we define a new way to
just template constancy? The compiler and programmer can easily reason
about that.
Now, you can just replace isObject with isConst, and we have the same
issue if constancy is allowed to be templated. This is kind of why I
didn't want to rely on templates to do tail-const -- there is just too
much power there.
Perhaps we can limit compile-time checking of this new const template
parameter. If the compiler detects any static check on the const
parameter, it doesn't allow implicit casting. Is that feasible?
-Steve

[...]
Thus both have their function, but in general I think that tail
const might be even more important (if I had to choose one const/
immutable type I would choose the tail one).

This was thought a bit as provocation, but as nobody reacted, and with
trolls roving around I want to clarify.
the normal const obviously allows sharing, so it isn't a bad choice,
but it introduces more constraints that needed to simply share memory.
These constraints make the life more difficult, so I wondered if
choosing tail const as only const would work.
It has issues, but not as bad as one would think, applying tail const
to ref T is basically const on T. There are still issues but I thought
lets throw that in and see what other think.
By the way I think that one of the problems in having const and tail
const is that it is expressing the constness implied by one operator
using the other operator, in this sense tail const might be (slightly)
better
Fawzi

I've also considered a template on the form
mixin tailConst!( SimpleRange, SimpleRange!( Tail!T ) );
or
mixin tailConst!( SimpleRange, Tail!T );

A closer look at this reveals that it won't work that simply, because
SimpleRange in this context is the struct, not the template. This,
however, works:
mixin tailConst!( .SimpleRange, TailT! );
Not sure how I like this.
--
Simen

* You may want to add more stringent checks for tailconst_t (well
TailConst etc) to make sure it's not bogus - has the same size,
compatible members etc.

I've tried, and it seems adding
static assert( T.sizeof == T.TailConst.sizeof );
does not work (no size yet for forward reference).
Checking that all members are the same type and order is easy and
works. Comparing member names bumped me into bug 5079. Likely related
to the above, seeing as both have to do with unfinished types.
I've also considered a template on the form
mixin tailConst!( SimpleRange, SimpleRange!( Tail!T ) );
or
mixin tailConst!( SimpleRange, Tail!T );
which would automagically define aliases. More work for me, perhaps
less work for users.
This could not define constructors, as overloads from inside template
mixins don't work with overloads outside.

Nobody ever commented on this. Figured I'd poke around with a stick
and see if there were any opinions. IOW: bump.
--
Simen

I've also considered a template on the form
mixin tailConst!( SimpleRange, SimpleRange!( Tail!T ) );
or
mixin tailConst!( SimpleRange, Tail!T );

A closer look at this reveals that it won't work that simply, because
SimpleRange in this context is the struct, not the template. This,
however, works:
mixin tailConst!( .SimpleRange, TailT! );
Not sure how I like this.

After more problems, I have also come to the conclusion that what is
most commonly needed is not really tail-const, but head-mutable. Why
this took me more than a few minutes to consider, I do not know.
Common use-cases would then look like this:
struct MyRange( Range ) {
HeadMutable!Range r;
// Range primitives, TailConst support, etc.
}

One might then think (I certainly did) that Tail(((Im|M)utable)|Const)
would be superfluous, and HeadMutable is the solution to all problems.
This is not true. HeadMutable is incapable of conveying that
TailMutable!T is implicitly castable to TailConst!T.
--
Simen