www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - tail const

reply Fawzi Mohamed <fawzi gmx.ch> writes:
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
Nov 30 2010
next sibling parent reply so <so so.do> writes:
 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/
Dec 01 2010
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday 01 December 2010 02:56:58 so wrote:
 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:
I proposed the following a while ago. First allow the class reference
 
 to (optionally) be made explicit:
         C a;     // mutable reference to mutable class
         C ref b; // mutable reference to mutable class
 
 And now you can apply tail-const to it:
         const(C)ref c;  // mutable reference to const class
         const(C ref) d; // const reference to const class
         const(C) e;     // const reference to const class
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
Dec 01 2010
next sibling parent so <so so.do> writes:
 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
Oh I see, reading Steven's post cleared it. Thanks! -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 01 2010
prev sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-01 06:17:24 -0500, Jonathan M Davis <jmdavisProg gmx.com> said:

 I proposed the following a while ago. First allow the class reference
 
 to (optionally) be made explicit:
 C a;     // mutable reference to mutable class
 C ref b; // mutable reference to mutable class
 
 And now you can apply tail-const to it:
 const(C)ref c;  // mutable reference to const class
 const(C ref) d; // const reference to const class
 const(C) e;     // const reference to const class
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/
Dec 01 2010
parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-01 09:37:08 -0500, Michel Fortin <michel.fortin michelf.com> said:

 On 2010-12-01 06:17:24 -0500, Jonathan M Davis <jmdavisProg gmx.com> said:
 
 I proposed the following a while ago. First allow the class reference
 
 to (optionally) be made explicit:
 C a;     // mutable reference to mutable class
 C ref b; // mutable reference to mutable class
 
 And now you can apply tail-const to it:
 const(C)ref c;  // mutable reference to const class
 const(C ref) d; // const reference to const class
 const(C) e;     // const reference to const class
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.
Turns out it's there's a trick that makes it much simpler than I expected. Patch coming soon. ;-) -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 01 2010
parent reply Fawzi Mohamed <fawzi gmx.ch> writes:
On 1-dic-10, at 20:07, Michel Fortin wrote:

 On 2010-12-01 09:37:08 -0500, Michel Fortin  
 <michel.fortin michelf.com> said:

 On 2010-12-01 06:17:24 -0500, Jonathan M Davis  
 <jmdavisProg gmx.com> said:
 I proposed the following a while ago. First allow the class  
 reference
 to (optionally) be made explicit:
 C a;     // mutable reference to mutable class
 C ref b; // mutable reference to mutable class
 And now you can apply tail-const to it:
 const(C)ref c;  // mutable reference to const class
 const(C ref) d; // const reference to const class
 const(C) e;     // const reference to const class
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.
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
Dec 02 2010
parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-02 05:57:18 -0500, Fawzi Mohamed <fawzi gmx.ch> said:

 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/
Dec 02 2010
next sibling parent Fawzi Mohamed <fawzi gmx.ch> writes:
On 2-dic-10, at 13:09, Michel Fortin wrote:

 On 2010-12-02 05:57:18 -0500, Fawzi Mohamed <fawzi gmx.ch> said:

 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/
Dec 02 2010
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 02 Dec 2010 07:09:27 -0500, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2010-12-02 05:57:18 -0500, Fawzi Mohamed <fawzi gmx.ch> said:

 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
Dec 02 2010
parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-02 16:14:58 -0500, "Steven Schveighoffer" 
<schveiguy yahoo.com> said:

 On Thu, 02 Dec 2010 07:09:27 -0500, Michel Fortin  
 <michel.fortin michelf.com> wrote:
 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/
Dec 02 2010
next sibling parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Michel Fortin <michel.fortin michelf.com> wrote:

 I'm not sure I get the problem. Can you show me in code?
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
Dec 02 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/2/10 6:54 PM, Simen kjaeraas wrote:
 Michel Fortin <michel.fortin michelf.com> wrote:

 I'm not sure I get the problem. Can you show me in code?
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
Dec 02 2010
next sibling parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:

 On 12/2/10 6:54 PM, Simen kjaeraas wrote:
 Michel Fortin <michel.fortin michelf.com> wrote:

 I'm not sure I get the problem. Can you show me in code?
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=
r
 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.
 What should work is this:

 const(int)[] data =3D [1,2,3];
 auto a =3D map!"a+a"(data);
 foreach (e;a) {
 }
That does work. import std.traits; /** * Return the tail-const type for a given type **/ template TailConst( T ) { static if ( is( T U : U[] ) ) { alias const(Unqual!U)[] TailConst; } else static if ( is( T U : U* ) ) { alias const(Unqual!U)* TailConst; } else static if ( is( T.tailconst_t ) ) { alias T.tailconst_t TailConst; } else static assert( false ); } unittest { struct test { alias int tailconst_t; } assert( is( TailConst!( int[] ) =3D=3D const(int)[] ) ); assert( is( TailConst!( immutable( int[] ) ) =3D=3D const(int)[] ) = ); assert( is( TailConst!( const(int)[] ) =3D=3D const(int)[] ) ); assert( is( TailConst!test =3D=3D int ) ); } /** * Converts the given parameter to tail const **/ TailConst!T tailconst( T )( T t ) { TailConst!T tmp =3D t; return tmp; } unittest { struct test( T ) { alias test!(const T) tailconst_t; this( const test!( Unqual!T ) t ) {} } assert( __traits( compiles, { tailconst( [1,2,3] ); } ) ); assert( __traits( compiles, { test!int t; tailconst( t ); } ) ); } -- = Simen
Dec 03 2010
parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Simen kjaeraas <simen.kjaras gmail.com> wrote:

 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.
To expound further on this, I have created the attached module. Critique wanted. -- = Simen
Dec 04 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/4/10 12:23 PM, Simen kjaeraas wrote:
 Simen kjaeraas <simen.kjaras gmail.com> wrote:

 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
Dec 04 2010
parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:

 On 12/4/10 12:23 PM, Simen kjaeraas wrote:

 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.
Done.
 * tailconst_t does not obey Phobos' naming convention. I think it's fine  
 to use TailConst in spite of the apparent ambiguity.
It adds some .'s: alias SimpleRange!(.TailConst!T) TailConst; static if ( !is( T == .TailMutable!T ) ) { this( SimpleRange.TailImmutable r ) { Not sure if this is a problem.
 * 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. -- Simen
Dec 05 2010
next sibling parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Simen kjaeraas <simen.kjaras gmail.com> wrote:

 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
Dec 05 2010
parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Simen kjaeraas <simen.kjaras gmail.com> wrote:

 Simen kjaeraas <simen.kjaras gmail.com> wrote:

 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. } -- Simen
Dec 26 2010
parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Simen kjaeraas <simen.kjaras gmail.com> wrote:

 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
Dec 26 2010
prev sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Simen kjaeraas <simen.kjaras gmail.com> wrote:

 Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:

 On 12/4/10 12:23 PM, Simen kjaeraas wrote:

 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.
Done.
 * tailconst_t does not obey Phobos' naming convention. I think it's fine
 to use TailConst in spite of the apparent ambiguity.
It adds some .'s: alias SimpleRange!(.TailConst!T) TailConst; static if ( !is( T == .TailMutable!T ) ) { this( SimpleRange.TailImmutable r ) { Not sure if this is a problem.
 * 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
Dec 15 2010
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 02 Dec 2010 22:17:53 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/2/10 6:54 PM, Simen kjaeraas wrote:
 Michel Fortin <michel.fortin michelf.com> wrote:

 I'm not sure I get the problem. Can you show me in code?
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
Dec 03 2010
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 02 Dec 2010 17:02:42 -0500, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2010-12-02 16:14:58 -0500, "Steven Schveighoffer"  
 <schveiguy yahoo.com> said:

 On Thu, 02 Dec 2010 07:09:27 -0500, Michel Fortin   
 <michel.fortin michelf.com> wrote:
 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?
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
Dec 03 2010
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/3/10 5:17 PM, Steven Schveighoffer wrote:
 On Thu, 02 Dec 2010 17:02:42 -0500, Michel Fortin
 <michel.fortin michelf.com> wrote:

 On 2010-12-02 16:14:58 -0500, "Steven Schveighoffer"
 <schveiguy yahoo.com> said:

 On Thu, 02 Dec 2010 07:09:27 -0500, Michel Fortin
 <michel.fortin michelf.com> wrote:
 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?
Here is an example range from dcollections (well, at least the pertinant part), for a linked list:
[snip analysis]
  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
Dec 03 2010
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 03 Dec 2010 19:06:36 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/3/10 5:17 PM, Steven Schveighoffer wrote:
 On Thu, 02 Dec 2010 17:02:42 -0500, Michel Fortin
 <michel.fortin michelf.com> wrote:

 On 2010-12-02 16:14:58 -0500, "Steven Schveighoffer"
 <schveiguy yahoo.com> said:

 On Thu, 02 Dec 2010 07:09:27 -0500, Michel Fortin
 <michel.fortin michelf.com> wrote:
 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?
Here is an example range from dcollections (well, at least the pertinant part), for a linked list:
[snip analysis]
  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
Dec 03 2010
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 03 Dec 2010 19:06:36 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/3/10 5:17 PM, Steven Schveighoffer wrote:
 On Thu, 02 Dec 2010 17:02:42 -0500, Michel Fortin
 <michel.fortin michelf.com> wrote:

 On 2010-12-02 16:14:58 -0500, "Steven Schveighoffer"
 <schveiguy yahoo.com> said:

 On Thu, 02 Dec 2010 07:09:27 -0500, Michel Fortin
 <michel.fortin michelf.com> wrote:
 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?
Here is an example range from dcollections (well, at least the pertinant part), for a linked list:
[snip analysis]
  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
Dec 03 2010
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/3/10 7:26 PM, Steven Schveighoffer wrote:
 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
Dec 03 2010
prev sibling next sibling parent Fawzi Mohamed <fawzi gmx.ch> writes:
On 4-dic-10, at 02:26, Steven Schveighoffer wrote:

 On Fri, 03 Dec 2010 19:06:36 -0500, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org 
 wrote:
 On 12/3/10 5:17 PM, Steven Schveighoffer wrote:
 On Thu, 02 Dec 2010 17:02:42 -0500, Michel Fortin
 <michel.fortin michelf.com> wrote:

 On 2010-12-02 16:14:58 -0500, "Steven Schveighoffer"
 <schveiguy yahoo.com> said:

 On Thu, 02 Dec 2010 07:09:27 -0500, Michel Fortin
 <michel.fortin michelf.com> wrote:
 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?
Here is an example range from dcollections (well, at least the pertinant part), for a linked list:
[snip analysis]
  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).
Dec 04 2010
prev sibling parent Fawzi Mohamed <fawzi gmx.ch> writes:
On 5-dic-10, at 00:39, Fawzi Mohamed wrote:

 [...]
 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
Dec 05 2010
prev sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-03 18:17:26 -0500, "Steven Schveighoffer" 
<schveiguy yahoo.com> said:

 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/
Dec 03 2010
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 03 Dec 2010 20:40:23 -0500, Michel Fortin  
<michel.fortin michelf.com> wrote:

 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
Dec 03 2010
parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-03 21:02:10 -0500, "Steven Schveighoffer" 
<schveiguy yahoo.com> said:

 On Fri, 03 Dec 2010 20:40:23 -0500, Michel Fortin  
 <michel.fortin michelf.com> wrote:
 
 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/
Dec 03 2010
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 03 Dec 2010 21:19:14 -0500, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2010-12-03 21:02:10 -0500, "Steven Schveighoffer"  
 <schveiguy yahoo.com> said:

 On Fri, 03 Dec 2010 20:40:23 -0500, Michel Fortin   
 <michel.fortin michelf.com> wrote:

 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.
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
Dec 03 2010
parent Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-03 21:25:01 -0500, "Steven Schveighoffer" 
<schveiguy yahoo.com> said:

 On Fri, 03 Dec 2010 21:19:14 -0500, Michel Fortin  
 <michel.fortin michelf.com> wrote:
 
 On 2010-12-03 21:02:10 -0500, "Steven Schveighoffer"  
 <schveiguy yahoo.com> said:
 
 On Fri, 03 Dec 2010 20:40:23 -0500, Michel Fortin   
 <michel.fortin michelf.com> wrote:
 
 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.
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/
Dec 03 2010
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/3/10 7:40 PM, Michel Fortin wrote:
 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'm afraid that can't work. struct A(T) { T obj; void fun() { obj->method(); } } auto a = new A!Widget; a.obj = new Widget; A!Object b = a; // works because Widget converts to Object b.obj = new Object; // ummmm... b.fun(); // ummmm... Andrei
Dec 03 2010
parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 04.12.2010 6:23, Andrei Alexandrescu wrote:
 On 12/3/10 7:40 PM, Michel Fortin wrote:
 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'm afraid that can't work. struct A(T) { T obj; void fun() { obj->method(); } }
Looks discouraging at first, but perfectly valid given that the example works only when A!Object compiles, i.e. Object have method 'method', and then:
 auto a = new A!Widget;
 a.obj = new Widget;
 A!Object b = *a; // works because Widget converts to Object
// A!Object* b = a; // should not compile, and would be a problem if A is a class //now we have another struct b with reference to a's widget
 b.obj = new Object; //no problem, a stays intact
 b.fun(); // since A!Object already compiles, it's perfectly valid
In fact, it looks like Michel's rule is very promising, just replace "struct/class" part with "struct" in definition. -- Dmitry Olshansky
Dec 03 2010
next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-04 02:48:26 -0500, Dmitry Olshansky <dmitry.olsh gmail.com> said:

 On 04.12.2010 6:23, Andrei Alexandrescu wrote:
 On 12/3/10 7:40 PM, Michel Fortin wrote:
 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'm afraid that can't work. struct A(T) { T obj; void fun() { obj->method(); } }
Looks discouraging at first, but perfectly valid given that the example works only when A!Object compiles, i.e. Object have method 'method', and then:
 auto a = new A!Widget;
 a.obj = new Widget;
 A!Object b = *a; // works because Widget converts to Object
// A!Object* b = a; // should not compile, and would be a problem if A is a class //now we have another struct b with reference to a's widget
 b.obj = new Object; //no problem, a stays intact
 b.fun(); // since A!Object already compiles, it's perfectly valid
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/
Dec 04 2010
parent Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-04 07:13:30 -0500, Michel Fortin <michel.fortin michelf.com> said:

 On 2010-12-04 02:48:26 -0500, Dmitry Olshansky <dmitry.olsh gmail.com> said:
 
 On 04.12.2010 6:23, Andrei Alexandrescu wrote:
 On 12/3/10 7:40 PM, Michel Fortin wrote:
 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'm afraid that can't work. struct A(T) { T obj; void fun() { obj->method(); } }
Looks discouraging at first, but perfectly valid given that the example works only when A!Object compiles, i.e. Object have method 'method', and then:
 auto a = new A!Widget;
 a.obj = new Widget;
 A!Object b = *a; // works because Widget converts to Object
// A!Object* b = a; // should not compile, and would be a problem if A is a class //now we have another struct b with reference to a's widget
 b.obj = new Object; //no problem, a stays intact
 b.fun(); // since A!Object already compiles, it's perfectly valid
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.
-- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 04 2010
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/4/10 1:48 AM, Dmitry Olshansky wrote:
 On 04.12.2010 6:23, Andrei Alexandrescu wrote:
 On 12/3/10 7:40 PM, Michel Fortin wrote:
 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'm afraid that can't work. struct A(T) { T obj; void fun() { obj->method(); } }
Looks discouraging at first, but perfectly valid given that the example works only when A!Object compiles, i.e. Object have method 'method', and then:
 auto a = new A!Widget;
 a.obj = new Widget;
 A!Object b = *a; // works because Widget converts to Object
// A!Object* b = a; // should not compile, and would be a problem if A is a class //now we have another struct b with reference to a's widget
 b.obj = new Object; //no problem, a stays intact
 b.fun(); // since A!Object already compiles, it's perfectly valid
In fact, it looks like Michel's rule is very promising, just replace "struct/class" part with "struct" in definition.
If conversion is allowed only for values (i.e. perform a memberwise copy of one struct to another), it looks like things could work. Almost. The problem is that that surreptitious copy completely bypasses the constructor: struct A(T) { private T obj; private bool isObject; this(T obj_) { obj = obj_; static if (is(T == Object)) isObject = true; } } auto a = A!Widget(new Widget); A!Object b = a; // works, new automatic conversion rule assert(!b.isObject); // passes, invariant is messed up Andrei
Dec 04 2010
next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-04 08:55:19 -0500, Andrei Alexandrescu 
<SeeWebsiteForEmail erdani.org> said:

 If conversion is allowed only for values (i.e. perform a memberwise 
 copy of one struct to another), it looks like things could work. 
 Almost. The problem is that that surreptitious copy completely bypasses 
 the constructor:
 
 struct A(T) {
      private T obj;
      private bool isObject;
      this(T obj_) {
          obj = obj_;
          static if (is(T == Object)) isObject = true;
      }
 }
 
 auto a = A!Widget(new Widget);
 A!Object b = a; // works, new automatic conversion rule
 assert(!b.isObject); // passes, invariant is messed up
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/
Dec 04 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/4/10 9:58 AM, Michel Fortin wrote:
 On 2010-12-04 08:55:19 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> said:

 If conversion is allowed only for values (i.e. perform a memberwise
 copy of one struct to another), it looks like things could work.
 Almost. The problem is that that surreptitious copy completely
 bypasses the constructor:

 struct A(T) {
 private T obj;
 private bool isObject;
 this(T obj_) {
 obj = obj_;
 static if (is(T == Object)) isObject = true;
 }
 }

 auto a = A!Widget(new Widget);
 A!Object b = a; // works, new automatic conversion rule
 assert(!b.isObject); // passes, invariant is messed up
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
Dec 04 2010
parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-04 11:06:14 -0500, Andrei Alexandrescu 
<SeeWebsiteForEmail erdani.org> said:

 On 12/4/10 9:58 AM, Michel Fortin wrote:
 On 2010-12-04 08:55:19 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> said:
 
 If conversion is allowed only for values (i.e. perform a memberwise
 copy of one struct to another), it looks like things could work.
 Almost. The problem is that that surreptitious copy completely
 bypasses the constructor:
 
 struct A(T) {
 private T obj;
 private bool isObject;
 this(T obj_) {
 obj = obj_;
 static if (is(T == Object)) isObject = true;
 }
 }
 
 auto a = A!Widget(new Widget);
 A!Object b = a; // works, new automatic conversion rule
 assert(!b.isObject); // passes, invariant is messed up
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/
Dec 04 2010
next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 05.12.2010 0:19, Michel Fortin wrote:
 On 2010-12-04 11:06:14 -0500, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> said:
 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))".
My thoughts exactly!
 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
Dec 04 2010
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/4/10 3:19 PM, Michel Fortin wrote:
 On 2010-12-04 11:06:14 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> said:

 On 12/4/10 9:58 AM, Michel Fortin wrote:
 On 2010-12-04 08:55:19 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> said:

 If conversion is allowed only for values (i.e. perform a memberwise
 copy of one struct to another), it looks like things could work.
 Almost. The problem is that that surreptitious copy completely
 bypasses the constructor:

 struct A(T) {
 private T obj;
 private bool isObject;
 this(T obj_) {
 obj = obj_;
 static if (is(T == Object)) isObject = true;
 }
 }

 auto a = A!Widget(new Widget);
 A!Object b = a; // works, new automatic conversion rule
 assert(!b.isObject); // passes, invariant is messed up
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
Dec 04 2010
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sat, 04 Dec 2010 08:55:19 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/4/10 1:48 AM, Dmitry Olshansky wrote:
 On 04.12.2010 6:23, Andrei Alexandrescu wrote:
 On 12/3/10 7:40 PM, Michel Fortin wrote:
 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'm afraid that can't work. struct A(T) { T obj; void fun() { obj->method(); } }
Looks discouraging at first, but perfectly valid given that the example works only when A!Object compiles, i.e. Object have method 'method', and then:
 auto a = new A!Widget;
 a.obj = new Widget;
 A!Object b = *a; // works because Widget converts to Object
// A!Object* b = a; // should not compile, and would be a problem if A is a class //now we have another struct b with reference to a's widget
 b.obj = new Object; //no problem, a stays intact
 b.fun(); // since A!Object already compiles, it's perfectly valid
In fact, it looks like Michel's rule is very promising, just replace "struct/class" part with "struct" in definition.
If conversion is allowed only for values (i.e. perform a memberwise copy of one struct to another), it looks like things could work. Almost. The problem is that that surreptitious copy completely bypasses the constructor: struct A(T) { private T obj; private bool isObject; this(T obj_) { obj = obj_; static if (is(T == Object)) isObject = true; } }
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
Dec 04 2010
prev sibling parent reply spir <denis.spir gmail.com> writes:
On Wed, 1 Dec 2010 03:17:24 -0800
Jonathan M Davis <jmdavisProg gmx.com> wrote:

 Various syntaxes have been proposed in the past. Syntax isn't really the =
issue.=20
 It's pretty easy to come up with one. I think that out of the ones I've s=
een,=20
 the I liked the best was the one proposed by Michel Fortin:
=20
I proposed the following a while ago. First allow the class reference
=20
 to (optionally) be made explicit:
         C a;     // mutable reference to mutable class
         C ref b; // mutable reference to mutable class
=20
 And now you can apply tail-const to it:
         const(C)ref c;  // mutable reference to const class
         const(C ref) d; // const reference to const class
         const(C) e;     // const reference to const class =20
This is the nicest proposal, imo as well. Is "ref" used here only because "C * b" would mean double indirection? Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 01 2010
parent Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-01 09:12:00 -0500, spir <denis.spir gmail.com> said:

 On Wed, 1 Dec 2010 03:17:24 -0800
 Jonathan M Davis <jmdavisProg gmx.com> wrote:
 
 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 s
een,
 the I liked the best was the one proposed by Michel Fortin:
 
 I proposed the following a while ago. First allow the class reference
 
 to (optionally) be made explicit:
 C a;     // mutable reference to mutable class
 C ref b; // mutable reference to mutable class
 
 And now you can apply tail-const to it:
 const(C)ref c;  // mutable reference to const class
 const(C ref) d; // const reference to const class
 const(C) e;     // const reference to const class
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/
Dec 01 2010
prev sibling next sibling parent reply vincent picaud <vincent.picaud laposte.net> writes:
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...
Dec 01 2010
parent reply vincent picaud <vincent.picaud laposte.net> writes:
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
Dec 01 2010
parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
vincent picaud <vincent.picaud laposte.net> wrote:

 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
Dec 01 2010
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, December 01, 2010 06:12:00 spir wrote:
 On Wed, 1 Dec 2010 03:17:24 -0800
 
 Jonathan M Davis <jmdavisProg gmx.com> wrote:
 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:
I proposed the following a while ago. First allow the class reference

 to (optionally) be made explicit:
         C a;     // mutable reference to mutable class
         C ref b; // mutable reference to mutable class
 
 And now you can apply tail-const to it:
         const(C)ref c;  // mutable reference to const class
         const(C ref) d; // const reference to const class
         const(C) e;     // const reference to const class
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
Dec 01 2010