www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - DMD 0.177 release

reply Walter Bright <newshound digitalmars.com> writes:
More ABI changes, and implicit [] => * no longer allowed.

http://www.digitalmars.com/d/changelog.html

http://ftp.digitalmars.com/dmd.175.zip
Dec 09 2006
next sibling parent "Rioshin an'Harthen" <rharth75 hotmail.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote:
 More ABI changes, and implicit [] => * no longer allowed.

 http://www.digitalmars.com/d/changelog.html

 http://ftp.digitalmars.com/dmd.175.zip
Awesome, just awesome... :)
Dec 09 2006
prev sibling next sibling parent "ideage" <lsina 126.com> writes:
Thank you , Walter! Great  D!





ideage. 
Dec 09 2006
prev sibling next sibling parent Thomas Kuehne <thomas-dloop kuehne.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Walter Bright schrieb am 2006-12-09:
 More ABI changes, and implicit [] => * no longer allowed.
Thanks, runtime reflection seems to be within reach now. Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFFeqR3LK5blCcjpWoRAuZpAJ4mS+QO5idDF7tKA9nw5d8aZ0xipwCffTYp hFGtpTzRynIPfEhg4qJam48= =EAfw -----END PGP SIGNATURE-----
Dec 09 2006
prev sibling next sibling parent Kyle Furlong <kylefurlong gmail.com> writes:
Walter Bright wrote:
 More ABI changes, and implicit [] => * no longer allowed.
 
 http://www.digitalmars.com/d/changelog.html
 
 http://ftp.digitalmars.com/dmd.175.zip
Good release. http://www.digitalmars.com/d/type.html is broken at the moment.
Dec 09 2006
prev sibling next sibling parent reply Endea <notknown none.com> writes:
Walter Bright kirjoitti:
 More ABI changes, and implicit [] => * no longer allowed.
 
 http://www.digitalmars.com/d/changelog.html
 
 http://ftp.digitalmars.com/dmd.175.zip
/home/me/D/Derelict/lib/libDerelictGL.a(glx.o):(.gnu.linkonce.d.D47TypeInfo_S8derelict6opengl3glx15__GLXconte tRec6__initZ+0x14): undefined reference to `D8derelict6opengl3glx15__GLXcontextRec6__initZ' glx.d does not define the struct, only it's name: struct __GLXcontextRec; So back to .176 for a while...
Dec 09 2006
parent Thomas Kuehne <thomas-dloop kuehne.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Endea schrieb am 2006-12-09:
 Walter Bright kirjoitti:
 More ABI changes, and implicit [] => * no longer allowed.
 
 http://www.digitalmars.com/d/changelog.html
 
 http://ftp.digitalmars.com/dmd.175.zip
/home/me/D/Derelict/lib/libDerelictGL.a(glx.o):(.gnu.linkonce.d.D47TypeInfo_S8derelict6opengl3glx15__GLXconte tRec6__initZ+0x14): undefined reference to `D8derelict6opengl3glx15__GLXcontextRec6__initZ' glx.d does not define the struct, only it's name: struct __GLXcontextRec; So back to .176 for a while...
The linux build of DMD seems to be a mix between version 0.176 and 0.177. Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFFesI+LK5blCcjpWoRAg+SAJsFITmQ0XjTOcSsDnxYaZIAICCgRgCfQvc5 kO1v0ikRMfz47MbJSw2P4lY= =Vbly -----END PGP SIGNATURE-----
Dec 09 2006
prev sibling next sibling parent nazo <lovesyao gmail.com> writes:
struct is value type so I don't like this change of cast and init.
I think that should introduce tuple literal as {} and support cast from 
tuple to struct like:

struct S{int x,y;}
S* s = &cast(S){0,0};//tuple literal + cast
*s = cast(S){0,0};//tuple literal + cast

#sorry for my poor English.
Dec 09 2006
prev sibling next sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Walter Bright wrote:
 More ABI changes, and implicit [] => * no longer allowed.
 
 http://www.digitalmars.com/d/changelog.html
 
 http://ftp.digitalmars.com/dmd.175.zip
It took me a moment to realise what you meant by this. This appears to be the latest answer to the multiple opCast problem, albeit only for when the target type is a struct. WIHYE could we please have at least an answer about this soon? http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D.bugs&article_id=9360 Stewart.
Dec 09 2006
parent reply Boris Kolar <boris.kolar globera.com> writes:
== Quote from Stewart Gordon (smjg_1998 yahoo.com)'s article


 It took me a moment to realise what you meant by this.
Can you please enlighten the rest of us too?
Dec 09 2006
parent Lars Ivar Igesund <larsivar igesund.net> writes:
Boris Kolar wrote:

 == Quote from Stewart Gordon (smjg_1998 yahoo.com)'s article


 It took me a moment to realise what you meant by this.
Can you please enlighten the rest of us too?
S(v) ==> S.opCall(v), that is calling the static opCall of struct S with v as an argument. -- Lars Ivar Igesund blog at http://larsivi.net DSource & #D: larsivi
Dec 09 2006
prev sibling next sibling parent reply "JohnC" <johnch_atms hotmail.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:ele2k9$2hr5$1 digitaldaemon.com...
 More ABI changes, and implicit [] => * no longer allowed.

 http://www.digitalmars.com/d/changelog.html

 http://ftp.digitalmars.com/dmd.175.zip
opAssign is new as well, but it throws an AV - unless I'm not using it right? class Test { int value; void opAssign(int value) { this.value = value; } } void main() { Test t = 10; }
Dec 09 2006
next sibling parent "JohnC" <johnch_atms hotmail.com> writes:
"JohnC" <johnch_atms hotmail.com> wrote in message 
news:elef3s$30jn$1 digitaldaemon.com...
 "Walter Bright" <newshound digitalmars.com> wrote in message 
 news:ele2k9$2hr5$1 digitaldaemon.com...
 More ABI changes, and implicit [] => * no longer allowed.

 http://www.digitalmars.com/d/changelog.html

 http://ftp.digitalmars.com/dmd.175.zip
opAssign is new as well, but it throws an AV - unless I'm not using it right? class Test { int value; void opAssign(int value) { this.value = value; } } void main() { Test t = 10; }
Take that back. Actually, it does work: Test t = new Test; t = 10; Sadly it doesn't like a struct as the lvalue. There are no examples in the docs, so how it's supposed to be used is anyone's guess...
Dec 09 2006
prev sibling parent Lutger <lutger.blijdestijn gmail.com> writes:
JohnC wrote:
  > opAssign is new as well, but it throws an AV - unless I'm not using it
 right?
 
 class Test {
 
   int value;
 
   void opAssign(int value) {
     this.value = value;
   }
 
 }
 
 void main() {
   Test t = 10;
 } 
 
This works: void main() { Test t = new Test; t = 10; } I'm surprised this overload is in, as I thought it was considered dangerous. Not that I'm complaining btw!
Dec 09 2006
prev sibling next sibling parent reply "Chris Miller" <chris dprogramming.com> writes:
Pretty cool stuff.

Suggestion:

Allow static opAssign to return an instance of the class that will be  =

assigned to the lvalue:
    class MyClass
    {
       this(int x) { }
       static MyClass opAssign(int x)
       {
          return new MyClass(x);
       }
    }
    // ...
    MyClass mc;
    mc =3D 3; // keeps mc null.

It seems logical to implement this proposal because to get the desired  =

effect right now you could do:
    mc =3D mc =3D 3;
Dec 09 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Chris Miller wrote:
 Allow static opAssign to return an instance of the class that will be 
 assigned to the lvalue:
This wasn't done because there are several use cases where you wouldn't want to erase all previous contents of the struct being assigned to.
Dec 09 2006
parent reply "Chris Miller" <chris dprogramming.com> writes:
On Sat, 09 Dec 2006 13:59:24 -0500, Walter Bright  
<newshound digitalmars.com> wrote:

 Chris Miller wrote:
 Allow static opAssign to return an instance of the class that will be  
 assigned to the lvalue:
This wasn't done because there are several use cases where you wouldn't want to erase all previous contents of the struct being assigned to.
But if it's optional it's up to the struct writer to use void as the return or not.
Dec 09 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Chris Miller wrote:
 On Sat, 09 Dec 2006 13:59:24 -0500, Walter Bright 
 <newshound digitalmars.com> wrote:
 
 Chris Miller wrote:
 Allow static opAssign to return an instance of the class that will be 
 assigned to the lvalue:
This wasn't done because there are several use cases where you wouldn't want to erase all previous contents of the struct being assigned to.
But if it's optional it's up to the struct writer to use void as the return or not.
I don't think that'll work. He'll wind up being forced to set all the fields.
Dec 09 2006
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Walter Bright wrote:
 Chris Miller wrote:
 On Sat, 09 Dec 2006 13:59:24 -0500, Walter Bright 
 <newshound digitalmars.com> wrote:
 
 Chris Miller wrote:
 Allow static opAssign to return an instance of the class that will 
 be assigned to the lvalue:
This wasn't done because there are several use cases where you wouldn't want to erase all previous contents of the struct being assigned to.
But if it's optional it's up to the struct writer to use void as the return or not.
I don't think that'll work. He'll wind up being forced to set all the fields.
Doesn't follow - there might not be any to set other than those that are an inherent part of the assignment operation. For example, consider an immutable big integer class. One might want Int x; ... x = 42; as syntactic sugar for x = new Int(42); Stewart.
Dec 11 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Stewart Gordon wrote:
 Walter Bright wrote:
 I don't think that'll work. He'll wind up being forced to set all the 
 fields.
Doesn't follow - there might not be any to set other than those that are an inherent part of the assignment operation.
But if there *are* other fields that shouldn't be set, a rule to preclude those cases would be a problem.
Dec 12 2006
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Walter Bright wrote:
 Stewart Gordon wrote:
 Walter Bright wrote:
 I don't think that'll work. He'll wind up being forced to set all the 
 fields.
Doesn't follow - there might not be any to set other than those that are an inherent part of the assignment operation.
But if there *are* other fields that shouldn't be set, a rule to preclude those cases would be a problem.
Exactly. *If* there are other fields that shouldn't be set. Do you even understand a word of what Chris is proposing? The programmer would have a choice - opAssign returning void to modify in-place the object referenced by the lvalue, or returning a new object that will be assigned to the lvalue. What is this precluding? Stewart.
Dec 13 2006
next sibling parent Sean Kelly <sean f4.ca> writes:
Stewart Gordon wrote:
 Walter Bright wrote:
 Stewart Gordon wrote:
 Walter Bright wrote:
 I don't think that'll work. He'll wind up being forced to set all 
 the fields.
Doesn't follow - there might not be any to set other than those that are an inherent part of the assignment operation.
But if there *are* other fields that shouldn't be set, a rule to preclude those cases would be a problem.
Exactly. *If* there are other fields that shouldn't be set. Do you even understand a word of what Chris is proposing? The programmer would have a choice - opAssign returning void to modify in-place the object referenced by the lvalue, or returning a new object that will be assigned to the lvalue. What is this precluding?
Just a few thoughts. If this change is implemented, it should apply to all assign ops, not just opAssign itself. So opAddAssign, opMulAssign, etc. Also, it seems like this could give rise to some interesting behavior: class MyClass { int val; this( int x ) { val = x; } MyClass opAssign( int x ) { return new MyClass( x ); } } void fn( MyClass c ) { c = 42; } MyClass c = new MyClass( 0 ); fn( c ); writefln( c.val ); Based on this suggestion, the above will print '0', not '42'. This is actually kind of an interesting situation, as it offers the possibility of making non-inout reference parameters immutable with respect to assign operations. The other consequence being that assign operations could change the address of a class reference as a side-effect, which may have an impact on optimization. It also may interact somewhat oddly with associative arrays and such, depending on how the compiler generates code. ie. MyClass[int] aa; aa[0] = new MyClass( 0 ); aa[0] = 42; writefln( aa[0].val ); What will the above print? This sort of behavior would need to be defined in the spec. Sean
Dec 13 2006
prev sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Stewart Gordon wrote:
 Walter Bright wrote:
 Stewart Gordon wrote:
 Walter Bright wrote:
 I don't think that'll work. He'll wind up being forced to set all 
 the fields.
Doesn't follow - there might not be any to set other than those that are an inherent part of the assignment operation.
But if there *are* other fields that shouldn't be set, a rule to preclude those cases would be a problem.
Exactly. *If* there are other fields that shouldn't be set. Do you even understand a word of what Chris is proposing? The programmer would have a choice - opAssign returning void to modify in-place the object referenced by the lvalue, or returning a new object that will be assigned to the lvalue. What is this precluding?
opAssign has 3 externally visible characteristics: 1) the parameter 2) the 'this' pointer 3) the return value Your proposal mixes up 2 and 3. opAssign works like: a = b becomes: a.opAssign(b) The return value is not assigned to a, it is the value of the expression (a = b). Mixing up the return value and the assignment to a will cause problems, as the two are different things, and should be independent.
Dec 13 2006
next sibling parent reply "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Walter Bright wrote:
 opAssign has 3 externally visible characteristics:
 
 1) the parameter
 2) the 'this' pointer
 3) the return value
 
 Your proposal mixes up 2 and 3. opAssign works like:
 
     a = b
 becomes:
     a.opAssign(b)
 
 The return value is not assigned to a, it is the value of the expression 
 (a = b). Mixing up the return value and the assignment to a will cause 
 problems, as the two are different things, and should be independent.
That makes me wonder - if a = b is used without picking up its result (the usual case), and if opAssign() returns an S, will the compiler optimize away the extra copy at zero cost? What I think happens is that the function will take a pointer to the destination which is zero, so a comparison will be made. But perhaps the optimizer will take care of eliminating that? Andrei
Dec 13 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Andrei Alexandrescu (See Website for Email) wrote:
 That makes me wonder - if a = b is used without picking up its result 
 (the usual case), and if opAssign() returns an S, will the compiler 
 optimize away the extra copy at zero cost?
No. The caller and callee don't know about each other.
 What I think happens is that the function will take a pointer to the 
 destination which is zero, so a comparison will be made. But perhaps the 
 optimizer will take care of eliminating that?
You could have a null pointer passed to the return result, but that would entail an extra check on the part of the callee. This is why a lot of C++ code tends to return references instead of values.
Dec 13 2006
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Walter Bright wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 
 That makes me wonder - if a = b is used without picking up its result 
 (the usual case), and if opAssign() returns an S, will the compiler 
 optimize away the extra copy at zero cost?
No. The caller and callee don't know about each other.
 What I think happens is that the function will take a pointer to the 
 destination which is zero, so a comparison will be made. But perhaps 
 the optimizer will take care of eliminating that?
You could have a null pointer passed to the return result, but that would entail an extra check on the part of the callee. This is why a lot of C++ code tends to return references instead of values.
I guess compiling internally two versions, one that returns S and one that returns void, might be worth looking into. Because right now user code for opAssign() can't be politically correct and efficient at the same time. Here's a better alternative: Require opAssign() to always return void and have the compiler return a reference to the left-hand side. That is, transform: a = b; into: (a.opAssign(b), a); with the mention that a only gets evaluated once. This way assignment _always_ returns its left-hand side and user code cannot subvert that behavior. Also the code will be efficient because no more spurious copies are being made. Andrei
Dec 13 2006
next sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 I guess compiling internally two versions, one that returns S and one 
 that returns void, might be worth looking into. Because right now user 
 code for opAssign() can't be politically correct and efficient at the 
 same time.
 
 Here's a better alternative:
 
 Require opAssign() to always return void and have the compiler return a 
 reference to the left-hand side. That is, transform:
 
 a = b;
 
 into:
 
 (a.opAssign(b), a);
 
 with the mention that a only gets evaluated once.
 
 This way assignment _always_ returns its left-hand side and user code 
 cannot subvert that behavior. Also the code will be efficient because no 
 more spurious copies are being made.
It is possible to force the return type of opAssign. But I'd suggest making it an S*, with the rewrite to: *(a.opAssign(b))
Dec 14 2006
next sibling parent reply Lionello Lunesu <lio lunesu.remove.com> writes:
Walter Bright wrote:
 It is possible to force the return type of opAssign. But I'd suggest 
 making it an S*, with the rewrite to:
 
     *(a.opAssign(b))
 
class C { C* opAssign(...) {...} } struct S { S* opAssign(...) {...} } Seriously?? Ugh. Why not just "C opAssign" and "S opAssign"? In the opCall discussion you said yourself that the return value will be optimized. Am I missing something? L.
Dec 14 2006
next sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Lionello Lunesu wrote:
 Walter Bright wrote:
 It is possible to force the return type of opAssign. But I'd suggest 
 making it an S*, with the rewrite to:

     *(a.opAssign(b))
class C { C* opAssign(...) {...} } struct S { S* opAssign(...) {...} } Seriously?? Ugh.
It isn't necessary for classes, as they are already reference types.
 Why not just "C opAssign" and "S opAssign"? In the opCall discussion you 
 said yourself that the return value will be optimized.
 
 Am I missing something?
Yes. The issue is that opAssign has both a return value *and* copies values into it's 'this' pointer.
Dec 14 2006
parent reply "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Walter Bright wrote:
 Why not just "C opAssign" and "S opAssign"? In the opCall discussion 
 you said yourself that the return value will be optimized.

 Am I missing something?
Yes. The issue is that opAssign has both a return value *and* copies values into it's 'this' pointer.
This is wrong. The assignment should return an lvalue, and the current semantics of opAssign do not allow that. Here is some code: void Increment(inout int x) { ++x; } int a; Increment(a = 5); This code leaves a containing the value 6. This is because a is first assigned a 5, then a is passed __as an lvalue__ to Increment, which bumps it. Now consider: struct S { int a; S opAssign(int x) { a = x; return this; } } void Increment(inout S x) { ++x.a; } S a; Increment(a = 5); This is going to have very different (and useless and unwanted) semantics. Again, the right thing to do: give the Caesar what belongs to the Caesar. Have the user do the assignment (and return void), and have the compiler pass the lhs lvalue around, when needed. Andrei
Dec 14 2006
parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Andrei Alexandrescu (See Website for Email) > Now consider:
 struct S {
    int a;
    S opAssign(int x) {
      a = x;
      return this;
    }
 }
 void Increment(inout S x) {
    ++x.a;
 }
 S a;
 Increment(a = 5);
 This is going to have very different (and useless and unwanted) semantics.
 Again, the right thing to do: give the Caesar what belongs to the
 Caesar. Have the user do the assignment (and return void), and have the
 compiler pass the lhs lvalue around, when needed.
 Andrei
This reminds me of the "opIndex" discussion we had here quite a while ago. The problem was that for user defined containers, you can't simulate X[i] if the contained type is something like a struct. class C { S s1; S opIndex(int i) { return s1; } } There is an opIndexAssign(), but it has the same problem when applied to the inout parameter. From the discussion at the time (Ben Hinkle might remember) I recall two possibilities that seemed to make sense to me. 1. Add a return type qualifier with "inout" semantics, like a C++ "&" type. Something like one of these, inout of course looks a bit funny: ref S opIndexRef(int i); inout S opIndexRef(int i); 2. Return S* and let the compiler apply the "*". S* opIndex(int i); Compiler silently transforms foo(x[i]) into foo(*x[i]) A* A::opAssign(inout A b); S* A::opIndexAssign(int i); assign1: a.opAssign(b) -> (a.opAssign(b), a) assign2: a.opAssign(b) -> *(a.opAssign(b)) index1: a.opIndexAssign(i) -> *opIndexAssign(i) Where index1 is my opIndex suggestion (from way back) and assign1 and assign2 are Walter and Andrei's possible syntaxes for opAssign() respectively. Conceptually, the problem looks similar to me: how to "tunnel" an assignable type through a return value of a method, like the C++ "&" types do. The opIndex version needs to tunnel up through more layers than opAssign() does since it's not the A type, so the (a.opIndex(b), a) can't work there of course. I would vote for "S*" being the conceptual return type in both cases for opAssign() and opIndex(), although in the case of opAssign() it wouldn't bother me if the user visible signature was "void opAssign(...)" and the compiler handles the pointer as it does with this(). Kevin
Dec 14 2006
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Kevin Bealer wrote:
 This reminds me of the "opIndex" discussion we had here quite a while ago.  The
 problem was that for user defined containers, you can't simulate X[i] if the
 contained type is something like a struct.
 
 class C {
    S s1;
 
    S opIndex(int i)
    {
       return s1;
    }
 }
 
 There is an opIndexAssign(), but it has the same problem when applied to the
inout
 parameter.
Hm. This inability to return lvalues looks like a serious loophole within D's type system.
 1. Add a return type qualifier with "inout" semantics, like a C++ "&" type.
 
     Something like one of these, inout of course looks a bit funny:
 
     ref S opIndexRef(int i);
     inout S opIndexRef(int i);
This will have serious ripples through the type system. For example, people might start to ask themselves whether they can define standalone inout variables, and what that means. Or, tell inside a template whether it received an inout parameter or not. At any rate, Perl 5 has implemented this exact hack, and seems to be working with it. See: http://search.cpan.org/~nwclark/perl-5.8.7/pod/perlsub.pod#Lvalue_subroutines The Perl guys seem to be unhappy about it because they labeled it as "experimental" in Perl 5 and they discuss eliminating it in Perl 6, in favor of some even more exotic features. See: http://dev.perl.org/perl6/rfc/149.html
 2. Return S* and let the compiler apply the "*".
 
     S* opIndex(int i);
     Compiler silently transforms foo(x[i]) into foo(*x[i])
This is also (probably more) unsatisfactory. People will be able to index and obtain lvalues, but will be unable to return something assignable from a function: ++s[a]; // possible through a compiler hack ++s(a, b); // impossible
 Conceptually, the problem looks similar to me: how to "tunnel" an assignable
type
 through a return value of a method, like the C++ "&" types do.  The opIndex
 version needs to tunnel up through more layers than opAssign() does since it's
not
 the A type, so the (a.opIndex(b), a) can't work there of course.
 
 I would vote for "S*" being the conceptual return type in both cases for
 opAssign() and opIndex(), although in the case of opAssign() it wouldn't
bother me
 if the user visible signature was "void opAssign(...)" and the compiler handles
 the pointer as it does with this().
returning inout is better, but I foresee a bunch of issues with it. The question whether lvalues are needed beyond function return values must be satisfactorily answered. Andrei
Dec 14 2006
next sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:

 returning inout is better, but I foresee a bunch of issues with it. The
 question whether lvalues are needed beyond function return values must
 be satisfactorily answered.
Ok, I think I understand the issue now. Also, it only applies to struct types, not class types. Class types are already reference types, so for them it's a non-issue.
Dec 14 2006
parent Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Walter Bright (newshound digitalmars.com)'s article
 Andrei Alexandrescu (See Website For Email) wrote:

 returning inout is better, but I foresee a bunch of issues with it. The
 question whether lvalues are needed beyond function return values must
 be satisfactorily answered.
Ok, I think I understand the issue now. Also, it only applies to struct types, not class types. Class types are already reference types, so for them it's a non-issue.
In the case of opIndex(), I think it does affect classes too. If I do this: void Swap(inout T a, inout T b) { T c=a; a=b; b=c; } class Vector { ... }; Vector X; 1. X[i].increment(); 2. Swap(X[i], X[j]); Kevin
Dec 15 2006
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Andrei Alexandrescu (See Website For Email)
(SeeWebsiteForEmail erdani.org)'s article
 Kevin Bealer wrote:
...
 2. Return S* and let the compiler apply the "*".

     S* opIndex(int i);
     Compiler silently transforms foo(x[i]) into foo(*x[i])
This is also (probably more) unsatisfactory. People will be able to index and obtain lvalues, but will be unable to return something assignable from a function: ++s[a]; // possible through a compiler hack ++s(a, b); // impossible
I missed this possibility. In order to solve just this next step { ++s(a, b); }, it seems like D would need something that copies like a pointer to either a struct or class reference, but automatically turns into (or acts like) a regular reference or LValue when something like ++ is applied. This means something in between a pointer and an LValue. I'll keep calling it 'ref' for now. I think to get the semantics we'd expect it needs these properties: 1. For struct, it can be returned from a method/function or passed as an (inout) argument one or more times without causing any struct-copy to happen, and still referring to the original class reference (not just the original class). 2. For class, it can be used to modify the original object reference itself, i.e. for: ref C X::opIndex(int i); foo(inout C); foo(x[i]) should have the potential to modify the reference in the i'th location of x, by replacing with a different C object. 3. It should autoconvert to (or behave like) a regular LValue when something like ++ or += happens. Of course if += is called and opAddAssign() returns "ref T", then the returned reference would propagate a ref to the same original entity. perfectly okay). Or assignment to the ref itself (the assign and other operations would by applied to the referred object of course). It's also probably convertable to a pointer via "&", i.e. address-of returns a pointer-to-original instead of pointer-to-ref. I guess this means it's impossible to get an address of the "ref" type itself except via trickery? I kind of hate to say it, but given all this, how far am I from describing a C++ "&" type? Is there anything the C++ type does that isn't in the above list? (Other than the new proposal for '& &' references I guess, which seems unnecessary for D.) Kevin
Dec 15 2006
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website For Email)
 (SeeWebsiteForEmail erdani.org)'s article
 ++s[a]; // possible through a compiler hack
 ++s(a, b); // impossible
I missed this possibility. In order to solve just this next step { ++s(a, b); }, it seems like D would need something that copies like a pointer to either a struct or class reference, but automatically turns into (or acts like) a regular reference or LValue when something like ++ is applied. This means something in between a pointer and an LValue.
I think it's an lvalue all right. Inside the compiler it's very clear what's an lvalue and what's not. Think of this: int x; Wherever you use x, even if you pass it to functions etc., the compiler entirely knows it's an lvalue. If, on the other hand, you use (x + 1), all of a sudden the compiler understands that's an rvalue, and accordingly won't let you e.g. pass it as an out or inout parameter. I was quite worried last night about this issue, but now I think I realize that returning from a function is about the only (I hope!) case in which the lvalue yes/no information about a value is lost. That is, you can't today write a function ident() that is totally transparent - i.e., can be added around any expression with no effect whatsoever: template ident(T) { T ident(T rvalue) { // works return rvalue; } T ident(inout T lvalue) { // loss of information return lvalue; } } By the way, ident() is a good function to check a language's quality. If it can't be implemented at all or efficiently, then the language is not ideal. Most languages can implement ident() but not efficiently. C++ came close to implementing it properly, but all the confusion between rvalues and const references made implementation exceedingly difficult (to add insult to injury, built-in rvalues are treated differently than user-defined rvalues). Current D cannot implement ident at all. I also wonder whether overloading on inout is possible. I think not, and if not, that is a good urgent item to put on the list. The code that should compile and execute correctly is: template ident(T) { T ident(T rvalue) { // works return rvalue; } inout T ident(inout T lvalue) { // works return lvalue; } }
 I kind of hate to say it, but given all this, how far am I from describing a
C++
 "&" type?   Is there anything the C++ type does that isn't in the above list?
 (Other than the new proposal for '& &' references I guess, which seems
unnecessary
 for D.)
There are a few differences that make all the difference :o). One is that C++ is too eager to convert rvalues to const &, which has caused an unbounded amount of harm. Second, in C++, T& is a type indeed, but it's a half-life type, a pariah. So it would be probably wise to do the entire lvalue/rvalue distinction without making references into types. For example, outside function declarations, there should be no other place where inout can be used. Andrei
Dec 15 2006
next sibling parent Sean Kelly <sean f4.ca> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Kevin Bealer wrote:
 
 I kind of hate to say it, but given all this, how far am I from 
 describing a C++
 "&" type?   Is there anything the C++ type does that isn't in the 
 above list?
 (Other than the new proposal for '& &' references I guess, which seems 
 unnecessary
 for D.)
There are a few differences that make all the difference :o). One is that C++ is too eager to convert rvalues to const &, which has caused an unbounded amount of harm. Second, in C++, T& is a type indeed, but it's a half-life type, a pariah. So it would be probably wise to do the entire lvalue/rvalue distinction without making references into types. For example, outside function declarations, there should be no other place where inout can be used.
Agreed. In my time using D, the only time I've wished for a reference type is for function return values. The language doesn't need a general reference qualifier. Besides, we've already got inout parameter types, and it seems completely reasonable that one should be able to pass references out of a function as well as into a function. Sean
Dec 15 2006
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Andrei Alexandrescu (See Website For Email)
(SeeWebsiteForEmail erdani.org)'s article
 Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website For Email)
 (SeeWebsiteForEmail erdani.org)'s article
 ++s[a]; // possible through a compiler hack
 ++s(a, b); // impossible
I missed this possibility. In order to solve just this next step { ++s(a, b); }, it seems like D would need something that copies like a pointer to either a struct or class reference, but automatically turns into (or acts like) a regular reference or LValue when something like ++ is applied. This means something in between a pointer and an LValue.
I think it's an lvalue all right. Inside the compiler it's very clear what's an lvalue and what's not. Think of this: int x; Wherever you use x, even if you pass it to functions etc., the compiler entirely knows it's an lvalue. If, on the other hand, you use (x + 1), all of a sudden the compiler understands that's an rvalue, and accordingly won't let you e.g. pass it as an out or inout parameter. I was quite worried last night about this issue, but now I think I realize that returning from a function is about the only (I hope!) case in which the lvalue yes/no information about a value is lost. That is, you can't today write a function ident() that is totally transparent - i.e., can be added around any expression with no effect whatsoever: template ident(T) { T ident(T rvalue) { // works return rvalue; } T ident(inout T lvalue) { // loss of information return lvalue; } } By the way, ident() is a good function to check a language's quality. If it can't be implemented at all or efficiently, then the language is not ideal. Most languages can implement ident() but not efficiently. C++ came close to implementing it properly, but all the confusion between rvalues and const references made implementation exceedingly difficult (to add insult to injury, built-in rvalues are treated differently than user-defined rvalues). Current D cannot implement ident at all. I also wonder whether overloading on inout is possible. I think not, and if not, that is a good urgent item to put on the list. The code that should compile and execute correctly is: template ident(T) { T ident(T rvalue) { // works return rvalue; } inout T ident(inout T lvalue) { // works return lvalue; } }
 I kind of hate to say it, but given all this, how far am I from describing a
C++
 "&" type?   Is there anything the C++ type does that isn't in the above list?
 (Other than the new proposal for '& &' references I guess, which seems
unnecessary
 for D.)
There are a few differences that make all the difference :o). One is that C++ is too eager to convert rvalues to const &, which has caused an unbounded amount of harm. Second, in C++, T& is a type indeed, but it's a half-life type, a pariah. So it would be probably wise to do the entire lvalue/rvalue distinction without making references into types. For example, outside function declarations, there should be no other place where inout can be used. Andrei
This is really interesting stuff. You've probably seen these already but lately D has gotten some compile time introspection stuff via tuples, in particular I'm thinking of (http://www.digitalmars.com/d/tuple.html) and (http://www.digitalmars.com/d/phobos/std_traits.html), which I think look quite useful. So I'm imagining a case of "meta-ident", which is to say, a template cousin of the ident test, applying the 'wrapper' concept to metaprogramming instead of programming. Knowing only the name of a method of a parameter class method (T::run), I want to duplicate it's signature in a template class method (A::meta). template A(T) { class A { T wrapped; this(T w) { wrapped = w; } ReturnType!(T.run) meta1(ParameterType!(T.run) i) { return wrapped.run(i); } inout ReturnType!(T.run) meta2(inout ParameterType!(T.run) i) { return wrapped.run(i); } } } Looking just at meta1, I'm wondering if it can absorbs the LValue-ness (inout qualification) through the Tuple. I'm thinking that it can't. If the LValue return type concept only affects function signatures as you say, then it can't be stored in a tuple, right? Instead it would get the type minus the inout qualifier? Now consider these two definitions of T::run(): struct S {...} S T::run1(in S x); inout S T::run2(inout S x); If I want to write a perfect-forwarding wrapper for run2, I need to use something like meta2, so that the LValue-ness is preserved. But meta2 can't work with run1, because the local S returned by run1 can't be returned via an inout return value. It would be returning a ref to a local var. So I need to know if the function takes and returns out vs. inout, in order to wrap it up like meta() is trying to do. This looks a bit like the C++ problem of having to define const and non-const versions of all the operators, except that meta1 or meta2 will do the wrong thing *silently* in these cases, either returning references to local vars (meta2 + run1), or discarding LValue-ness (meta1 + run2). Maybe I'm solving a non-problem here: in most cases the user should know if T::run() returns an LValue or RValue; for example, opIndex() could be understood to always return an LValue by convention. It seems like this local case could be solved by making ReturnType!() and ParameterTypeTuple!() into language attributes, so that they could see the fully qualified function-signature grade types with passing conventions etc. T::run.return_type_of meta_all(T::run.parameter_type_of[0..$] x) { // pass all the parameters via some kind of compiler-aware tuple return wrapped.run(x[0..$]); } Which could presumably, provide perfect forwarding of T::run() when used in a function signature context. [Or maybe there's always a conceptual 'leak' in these kinds of systems, and it can only be pushed a finite distance toward obscurity with each new language feature. I hope not, but...] Kevin
Dec 15 2006
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Kevin Bealer wrote:
 This is really interesting stuff.  You've probably seen these already but
lately D
 has gotten some compile time introspection stuff via tuples, in particular I'm
 thinking of (http://www.digitalmars.com/d/tuple.html) and
 (http://www.digitalmars.com/d/phobos/std_traits.html), which I think look quite
 useful.
I'm familiar with those :o).
 So I'm imagining a case of "meta-ident", which is to say, a template cousin of
the
 ident test, applying the 'wrapper' concept to metaprogramming instead of
 programming.  Knowing only the name of a method of a parameter class method
 (T::run), I want to duplicate it's signature in a template class method
(A::meta).
[snip]
 Looking just at meta1, I'm wondering if it can absorbs the LValue-ness (inout
 qualification) through the Tuple.  I'm thinking that it can't.  If the LValue
 return type concept only affects function signatures as you say, then it can't
be
 stored in a tuple, right?  Instead it would get the type minus the inout
qualifier?
Your observation is correct. It all boils down to this: if "inout" is not part of the type system, there are two issues: (1) two overloads will always be needed to catch it (the similar way it's needed to overload the same function on const/non-const), and also (2) compile-time introspection would be more complicated as you need to "catch" the inout part of the parameter separately - the type won't be part of it.
 It seems like this local case could be solved by making ReturnType!() and
 ParameterTypeTuple!() into language attributes, so that they could see the
fully
 qualified function-signature grade types with passing conventions etc.
 
   T::run.return_type_of  meta_all(T::run.parameter_type_of[0..$] x)
   {
       // pass all the parameters via some kind of compiler-aware tuple
       return wrapped.run(x[0..$]);
   }
 
 Which could presumably, provide perfect forwarding of T::run() when used in a
 function signature context.
 
 [Or maybe there's always a conceptual 'leak' in these kinds of systems, and it
can
 only be pushed a finite distance toward obscurity with each new language
feature.
  I hope not, but...]
That remains to be seen, but I think the buck stops at functions. The problem of duplicating templates for inout (lvalues) and rvalues stays, but I have an idea about that, that I might tell about tomorrow. Andrei
Dec 15 2006
next sibling parent Georg Wrede <georg.wrede nospam.org> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Kevin Bealer wrote:
 
 This is really interesting stuff.  You've probably seen these already 
 but lately D has gotten some compile time introspection stuff 
 via tuples, in particular I'm thinking of 
 
 (http://www.digitalmars.com/d/tuple.html) and
 (http://www.digitalmars.com/d/phobos/std_traits.html), 

 which I think look quite useful.
I'm familiar with those :o).
LOL! Incidentally, the technical expertise, theoretical depth, and rigor of thinking that you've brought with you, have been sorely missed here. We'd be nowhere near this far without your already big contributions to D! I can hardly imagine what we've got ahead of us!
Dec 15 2006
prev sibling next sibling parent Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Andrei Alexandrescu (See Website For Email)
(SeeWebsiteForEmail erdani.org)'s article
 Kevin Bealer wrote:
 This is really interesting stuff.  You've probably seen these already but
lately D
 has gotten some compile time introspection stuff via tuples, in particular I'm
 thinking of (http://www.digitalmars.com/d/tuple.html) and
 (http://www.digitalmars.com/d/phobos/std_traits.html), which I think look quite
 useful.
I'm familiar with those :o).
Yes, I have this red covered book around here that goes into more depth, but I can never find it... :)
 So I'm imagining a case of "meta-ident", which is to say, a template cousin of
the
 ident test, applying the 'wrapper' concept to metaprogramming instead of
 programming.  Knowing only the name of a method of a parameter class method
 (T::run), I want to duplicate it's signature in a template class method
(A::meta).
[snip]
 Looking just at meta1, I'm wondering if it can absorbs the LValue-ness (inout
 qualification) through the Tuple.  I'm thinking that it can't.  If the LValue
 return type concept only affects function signatures as you say, then it can't
be
 stored in a tuple, right?  Instead it would get the type minus the inout
qualifier?
 Your observation is correct. It all boils down to this: if "inout" is
 not part of the type system, there are two issues: (1) two overloads
 will always be needed to catch it (the similar way it's needed to
 overload the same function on const/non-const), and also (2)
 compile-time introspection would be more complicated as you need to
 "catch" the inout part of the parameter separately - the type won't be
 part of it.

 It seems like this local case could be solved by making ReturnType!() and
 ParameterTypeTuple!() into language attributes, so that they could see the
fully
 qualified function-signature grade types with passing conventions etc.

   T::run.return_type_of  meta_all(T::run.parameter_type_of[0..$] x)
   {
       // pass all the parameters via some kind of compiler-aware tuple
       return wrapped.run(x[0..$]);
   }

 Which could presumably, provide perfect forwarding of T::run() when used in a
 function signature context.

 [Or maybe there's always a conceptual 'leak' in these kinds of systems, and it
can
 only be pushed a finite distance toward obscurity with each new language
feature.
  I hope not, but...]
That remains to be seen, but I think the buck stops at functions. The problem of duplicating templates for inout (lvalues) and rvalues stays, but I have an idea about that, that I might tell about tomorrow. Andrei
That hint got me thinking and I had a look for myself at whether the current system can be used to make the distinction between in and inout. It took me a while to nail down a syntax that could do it. Of course, I can't test it with "inout" return types since we don't have those to play with, but I think what I have here might work for them if they were available. (Maybe inout is a bad name for a return type, but...) This is what I was able to make: import std.stdio; import std.traits; class Refer { alias int Item; Item run(inout Item x) { writefln("run(io)"); x += 300; return 10; } }; class Value { alias int Item; Item run(Item x) { writefln("run(i)"); x += 400; return 20; } }; template Baz(T) { class Baz { T wr_; this() { wr_ = new T(); } alias T.Item Item; alias Item delegate(inout Item) run_IO; alias Item delegate(Item) run_I; alias typeof(& (new T).run) TRunFunc; static if (is(typeof(TRunFunc) == typeof(run_IO))) { Item walk(inout Item x) { writefln("walk(io)"); x += 5000; return wr_.run(x); } } static if (is(typeof(TRunFunc) == typeof(run_I))) { Item walk(Item x) { writefln("walk(i)"); x += 6000; return wr_.run(x); } } } } int main(char[][] args) { alias Baz!(Value) TValue; alias Baz!(Refer) TRefer; TValue v = new TValue; TRefer r = new TRefer; int a = 0; int b = v.walk(a); int c = 0; int d = r.walk(c); writefln("\nValue semantics %s, %s.", a, b); writefln("Refer semantics %s, %s.", c, d); return 0; } Output looks like: walk(i) run(i) walk(io) run(io) Value semantics 0, 20. Refer semantics 5300, 10. So, I can match and select the preferred signature via delegates and typeof etc. Of course, if everyone uses this, it would be good to have some kind of shortcut for the above syntax. Maybe the IsExpression could be expanded to accept types like this: is(T.run() : int function(inout int)) Right now this is accepted (compiles) but does not seem to work - it always seems to evaluate as false. Kevin
Dec 16 2006
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Andrei Alexandrescu (See Website For Email)
...
 That remains to be seen, but I think the buck stops at functions. The
 problem of duplicating templates for inout (lvalues) and rvalues
 stays, but I have an idea about that, that I might tell about
 tomorrow.

 Andrei
Did you ever work out how to do this lvalue/rvalue idea? Kevin
Dec 19 2006
parent reply "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website For Email)
 ...
 That remains to be seen, but I think the buck stops at functions. The
 problem of duplicating templates for inout (lvalues) and rvalues
 stays, but I have an idea about that, that I might tell about
 tomorrow.

 Andrei
Did you ever work out how to do this lvalue/rvalue idea?
I had to leave to Romania before having the time to post thoughts on the lvalue/rvalue discussion I've had with Walter on Saturday. We both agree that it's a serious problem with D's type system that needs fixing (and that he needs to do all the work :o)). I've continued to think of the issue, at least enough to figure that the solution we initially thought of is not sound. Walter is reluctant to offering the ability to overload on lvalue/rvalue, while it turns out that that can't be avoided. Let's return to my litmus test - the identity function ident(e), which can snuggle any expression e and leave its semantics unchanged. My thesis is that this function is an important test of a language's power. The starting point would be: template ident(T) { T ident(T e) { return e; } inout T ident(inout T e) { return e; } } The problem with this approach is that it doesn't scale. Right now "inout" is about the only interesting storage class of a function parameter, but "lazy" comes to mind (which I hope to get rid of soon via a much better solution) and later on we'll have "const", and each of these combinations will mean one more duplication of the ident body (and, by extension, of any function that wants to just pass the storage class outside). Walter had an idea along the line: template ident(T) { return T ident(return T e) { return e; } } which allows you to reuse the return keyword as a symbolic placeholder for passing out the storage class. This solution is severely shortsighted in that it fixes ident and only ident, whereas the purpose of ident is to serve as a simplified case for functions with multiple parameters. So this fell as well. We then discussed another solution that I won't bore you with, as it's so wrong it hurts. My current thoughts navigate around two possible solutions. One is to make the storage part of the template parameters: template ident(S T) { S T ident(S T e) { return e; } } When two adjacent symbols appear in a template parameter list, they unambiguously denote a storage class followed by a type. So "S" can bind to things like "in", "inout" etc., while "T" can bind to types. In the example above, the compiler will deduce both S and T from the argument type. It already does that, so that's no extra difficulty. The key point that makes this scale is that you can bind S and T multiple times in a variadic template. Another interesting detail is that it clarifies that you can't solve the problem without somehow compiling two versions of the ident function. So in the end overloading on "inout" is a must. Another solution that works is to commit to the idea of associating the storage class with the parameter (and divorce it from the type entirely). In that case, the following syntax describes what happens: template ident(T) { storageof(e) T ident(storageof(e) T e) { return e; } } The storageof(symbol) meta-operator yields the storage of that symbol. The problem with this notation is that it uses a symbol without having seen it. That's not too bad (it already happens due to the way symbols at global scope are looked up) but in this case it does have a fishy smell. Another thing that I don't like it that the code obscures what's going on - namely that one ident will be generated for each storage class, even though that's not reflected in the parameter type list. Finally, one related but slightly different topic is the necessity of deduced return types for functions, e.g. by using "auto" to denote the return type. Automatic deduction of return types is very useful in that it allows compact template function definition - no more need for a template that defines a homonym function. With deduced argument types, ident can be written as: auto ident(S T)(S T e) { return e; } which is, I think, the Platonic ideal of ident as far as expressing it in D goes. Andrei
Dec 20 2006
next sibling parent reply Lionello Lunesu <lio lunesu.remove.com> writes:
Andrei Alexandrescu (See Website for Email) wrote:
 Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website For Email)
 ...
 That remains to be seen, but I think the buck stops at functions. The
 problem of duplicating templates for inout (lvalues) and rvalues
 stays, but I have an idea about that, that I might tell about
 tomorrow.

 Andrei
Did you ever work out how to do this lvalue/rvalue idea?
<snip> I'd like "auto" to appear on more places as it will make templates more accessible than ever before. For example, this notation has been suggested a couple of times: void func(auto x) { } I feel confident that having you and Walter on the issue, we should have a nice solution in no-time :) Sarbatori fericite! L. I'm actually leaving Romania to go to the Netherlands :)
Dec 20 2006
parent reply "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Lionello Lunesu wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website For Email)
 ...
 That remains to be seen, but I think the buck stops at functions. The
 problem of duplicating templates for inout (lvalues) and rvalues
 stays, but I have an idea about that, that I might tell about
 tomorrow.

 Andrei
Did you ever work out how to do this lvalue/rvalue idea?
<snip> I'd like "auto" to appear on more places as it will make templates more accessible than ever before. For example, this notation has been suggested a couple of times: void func(auto x) { }
Thanks for the kind wishes. One problem I see with the shortcut above is that the notation does not clarify whether "auto" catches both the type and the storage class. Even if it does, more linguistic machinery is needed to extract the type and storage class of x and to properly transport them, if needed, to the return type. So in the end the "auto" suggestion is a mere shortcut for: void func(S T)(S T x) { } with the difference that the storage and type entities are clearly bound to symbols, style that's more in the spirit of D. Andrei
Dec 20 2006
parent reply Reiner Pope <xxxxxx xxx.xx> writes:
Andrei Alexandrescu (See Website for Email) wrote:
 Lionello Lunesu wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website For Email)
 ...
 That remains to be seen, but I think the buck stops at functions. The
 problem of duplicating templates for inout (lvalues) and rvalues
 stays, but I have an idea about that, that I might tell about
 tomorrow.

 Andrei
Did you ever work out how to do this lvalue/rvalue idea?
<snip> I'd like "auto" to appear on more places as it will make templates more accessible than ever before. For example, this notation has been suggested a couple of times: void func(auto x) { }
 So in the end the "auto" suggestion is a mere shortcut for:
 
 void func(S T)(S T x) { }
 
 with the difference that the storage and type entities are clearly bound 
 to symbols, style that's more in the spirit of D.
The motivation I see for such a shortcut is that this template pattern is used so often that it is worth simplifying. Furthermore, the variables S and T are often just dummy variables to satisfy the requirements of the template; you don't even need these symbols, and they fill the namespace. Cheers, Reiner
Dec 20 2006
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Reiner Pope wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 Lionello Lunesu wrote:
 I'd like "auto" to appear on more places as it will make templates 
 more accessible than ever before. For example, this notation has been 
 suggested a couple of times:

 void func(auto x) { }
 So in the end the "auto" suggestion is a mere shortcut for:

 void func(S T)(S T x) { }

 with the difference that the storage and type entities are clearly 
 bound to symbols, style that's more in the spirit of D.
The motivation I see for such a shortcut is that this template pattern is used so often that it is worth simplifying. Furthermore, the variables S and T are often just dummy variables to satisfy the requirements of the template; you don't even need these symbols, and they fill the namespace.
I'm definitely one for shortcuts, but let's not forget that at the moment we lack the feature that the shortcut is supposed to simplify. At this moment, "auto" helps solving the storage class parameterization issue as much as a great color for the seat covers helps designing an economic automobile. Let me illustrate further why ident is important and what solution we should have for it. Consider C's response to ident: #define IDENT(e) (e) Now, you can use IDENT with many C expressions (except those that have top-level commas), so we should be glad. Or should we? A true solution is to intercept the type of the expression and intelligently pass it as the return type of the function. In the general case, that means the language offers total control to the programmer of the types tossed around by a program. Instead, the C solution cheats its way around in that it has absolutely no grip on the expression type; it just gives the illusion that a function call is going on. Similarly, let's say that a group of revolutionaries convinces Walter (as I understand happened in case of using "length" and "$" inside slice expressions, which is a shame and an absolute disaster that must be undone at all costs) to implement "auto", therefore leading to the following implementation of ident: auto ident(auto x) { return x; } In the euphoria of the celebration, it will be initially forgotten that we have the shortcut without the feature, for the feature means having access to the exact type and storage of x, not finding a way to comfortably specify a deduction process. Andrei
Dec 20 2006
next sibling parent reply Don Clugston <dac nospam.com.au> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Similarly, let's say that a group of revolutionaries convinces Walter 
 (as I understand happened in case of using "length" and "$" inside slice 
 expressions, which is a shame and an absolute disaster that must be 
 undone at all costs) to implement "auto"
This off-hand remark worries me. I presume that you mean being able to reference the length of a string, from inside the slice? (rather than simply the notation). And the problem being that it requires a sliceable entity to know its length? Or is the problem more serious than that? It's worrying because any change would break an enormous amount of code. These issues you're raising seem to be far too fundamental to be fixed in the next few days, casting grave doubts on whether a D1.0 release on Jan 1 is a good idea.
Dec 20 2006
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Don Clugston wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Similarly, let's say that a group of revolutionaries convinces Walter 
 (as I understand happened in case of using "length" and "$" inside 
 slice expressions, which is a shame and an absolute disaster that must 
 be undone at all costs) to implement "auto"
This off-hand remark worries me. I presume that you mean being able to reference the length of a string, from inside the slice? (rather than simply the notation). And the problem being that it requires a sliceable entity to know its length? Or is the problem more serious than that? It's worrying because any change would break an enormous amount of code.
It would indeed break an enormous amount of code, but "all costs" includes "enormous costs". :o) A reasonable migration path is to deprecate them soon and make them illegal over the course of one year. A small book could be written on just how bad language design is using "length" and "$" to capture slice size inside a slice expression. I managed to write two lengthy emails to Walter about them, and just barely got started. Long story short, "length" introduces a keyword through the back door, effectively making any use of "length" anywhere unrecommended and highly fragile. Using "$" is a waste of symbolic real estate to serve a narrow purpose; the semantics isn't naturally generalized to its logical conclusion; and the choice of symbol itself been vastly better as it has count connotation in natural language, and making it into an operator would have fixed the generalization issue). As things stand now, the rules governing the popping up of "length" and "$" constitute a sudden boo-boo on an otherwise carefully designed expression landscape.
 These issues you're raising seem to be far too fundamental to be fixed 
 in the next few days, casting grave doubts on whether a D1.0 release on 
 Jan 1 is a good idea.
The lvalue/rvalue issue is fundamental. I'm not in the position to assess whether it's a maker or breaker of D 1.0. The "length"/"$" issue is not fundamental the same way that C's declaration syntax, Java's throw specifications, C++'s use of "<" and ">" for templates, and Mao Zedong's refusal to use a toothbrush are not fundamental. It will "just" go down in history as a huge embarrassment and a good resource for cheap shooters and naysayers. If I understand its genesis, it will also be a canonical example of why design by committee is bad. Andrei
Dec 20 2006
next sibling parent Dave <Dave_member pathlink.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Don Clugston wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Similarly, let's say that a group of revolutionaries convinces Walter 
 (as I understand happened in case of using "length" and "$" inside 
 slice expressions, which is a shame and an absolute disaster that 
 must be undone at all costs) to implement "auto"
This off-hand remark worries me. I presume that you mean being able to reference the length of a string, from inside the slice? (rather than simply the notation). And the problem being that it requires a sliceable entity to know its length? Or is the problem more serious than that? It's worrying because any change would break an enormous amount of code.
It would indeed break an enormous amount of code, but "all costs" includes "enormous costs". :o) A reasonable migration path is to deprecate them soon and make them illegal over the course of one year. A small book could be written on just how bad language design is using "length" and "$" to capture slice size inside a slice expression. I managed to write two lengthy emails to Walter about them, and just barely got started. Long story short, "length" introduces a keyword through the back door, effectively making any use of "length" anywhere unrecommended and highly fragile. Using "$" is a waste of symbolic real estate to serve a narrow purpose; the semantics isn't naturally generalized to its logical conclusion; and the choice of symbol itself been vastly better as it has count connotation in natural language, and making it into an operator would have fixed the generalization issue). As things stand now, the rules governing the popping up of "length" and "$" constitute a sudden boo-boo on an otherwise carefully designed expression landscape.
Are you suggesting either / both: slice = array[x .. array.length]; ? Since length and $ are pretty easily grep-able in the context of slice syntax, perhaps it's not a "huge" issue if these were deprecated now and then not supported in the span of a couple of 1.0.x releases or so (instead of a year)?
 These issues you're raising seem to be far too fundamental to be fixed 
 in the next few days, casting grave doubts on whether a D1.0 release 
 on Jan 1 is a good idea.
The lvalue/rvalue issue is fundamental. I'm not in the position to assess whether it's a maker or breaker of D 1.0.
Since one of the main drivers for 1.0 by Jan. 1, 2007 is to encourage / solidify library development, and since library design could be affected in a large way by this issue, I'd say it's best to figure this out before releasing 1.0.
 The "length"/"$" issue is not fundamental the same way that C's 
 declaration syntax, Java's throw specifications, C++'s use of "<" and 
 ">" for templates, and Mao Zedong's refusal to use a toothbrush are not 
 fundamental. It will "just" go down in history as a huge embarrassment 
 and a good resource for cheap shooters and naysayers. If I understand 
 its genesis, it will also be a canonical example of why design by 
 committee is bad.
 
If indeed it will be an embarrassment, better to take care of this sooner (pre-1.0) rather than later, IMHO. Thanks, - Dave
Dec 20 2006
prev sibling next sibling parent reply Derek Parnell <derek psych.ward> writes:
On Wed, 20 Dec 2006 06:24:28 -0800, Andrei Alexandrescu (See Website For
Email) wrote:

 
 A small book could be written on just how bad language design is using 
 "length" and "$" to capture slice size inside a slice expression. I 
 managed to write two lengthy emails to Walter about them, and just 
 barely got started. 
Please share your thoughts here if you can too.
 Long story short, "length" introduces a keyword 
 through the back door, effectively making any use of "length" anywhere 
 unrecommended and highly fragile. 
There is no arguement from me on that score.
 Using "$" is a waste of symbolic real 
 estate to serve a narrow purpose;
By that do you mean that the symbol "$" could be better utilized elsewhere in the language?
 the semantics isn't naturally generalized to its logical conclusion;
I have no idea what that statement means. The semantics of *what*? Define "naturally generalized" in absolute terms without recourse to opinion. What is the "logical conclusion" you talk of?
 and the choice of symbol itself 

 been vastly better as it has count connotation in natural language
The concept was to have a very short symbol to represent the array's being one of them. Walter rejected that one because it was already used for something else - the 'special token sequences' construct, such as "#line". Also symbols that consisted of identifier characters all have the same problem as "length" does. I favoured "$" because it was a single-character symbol and is already used in similar concepts inside regular expression syntaxes. Although the exact symbol that is 'finally' decided upon is not a burning issue for me, there would have to be a very solid argument for the provable superiority of that one symbol over the rest. And currently, the choice
 and making it into an operator would have fixed the generalization issue
Would this lead to an opLength() method available for overloading?
 As things stand now, the rules governing the popping up of "length" and 
 "$" constitute a sudden boo-boo on an otherwise carefully designed 
 expression landscape.
Still sounds like an opinion and not a fact, in my opinion ;-) -- Derek
Dec 20 2006
next sibling parent reply Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Derek Parnell wrote:
 The concept was to have a very short symbol to represent the array's

 being one of them. Walter rejected that one because it was already used for
 something else - the 'special token sequences' construct, such as "#line".
 Also symbols that consisted of identifier characters all have the same
 problem as "length" does. I favoured "$" because it was a single-character
 symbol and is already used in similar concepts inside regular expression
 syntaxes.
The "[..$]" syntax is also present in ColdC and its relatives (including my Bovis), so it was familiar to me from the beginning. (That said I still harbor thoughts that $ could be used for other things... but honestly, I think the syntax would be unambiguous: a lone $ as the right hand side of a slice expression should easily enough be distinguishable from a $ anywhere followed by something, like an identifier.) Which leads me to another thought. One other operator that ColdC and family posesses is the for list splicing. Useless sample ColdC: The 'result' variable now equals {1, 2, 3, 4, 5, 6}. Perhaps we could get something similar in D, also using , and then I think would possibly be a logical choice for denoting "until end" in slices. I also think an 'opLength' operator is not a bad idea, but it might be best just to require a .length property of some kind be present, since presumably any class that exposes itself to slicing would likely also have a length concept of some kind. (Contrary examples welcome.) -- Chris Nicholson-Sauls
Dec 20 2006
parent reply BCS <BCS pathilink.com> writes:
Chris Nicholson-Sauls wrote:
 The "[..$]" syntax is also present in ColdC and its relatives (including 
 my Bovis), so it was familiar to me from the beginning.  (That said I 
 still harbor thoughts that $ could be used for other things... but 
 honestly, I think the syntax would be unambiguous: a lone $ as the right 
 hand side of a slice expression should easily enough be distinguishable 
 from a $ anywhere followed by something, like an identifier.)
 
FWIW $ is not only used for the RHS of a slice char[] str; str[$/2..$]; // 2nd half of array str[$-1]; // last element in array str[$-5..$]; // last 5 things in array str[$-10..10]; // um... well... you get the idea
 Which leads me to another thought.  One other operator that ColdC and 
 family posesses is the   for list splicing.  Useless sample ColdC:
 





 
 The 'result' variable now equals {1, 2, 3, 4, 5, 6}.
I would think this would be the same thing. auto foo = [1,2,3]; auto bar = [4,5,6]; auto result = foo ~ bar; am I missing somethign?
 
 -- Chris Nicholson-Sauls
Dec 20 2006
parent reply Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
BCS wrote:
 Chris Nicholson-Sauls wrote:
 The "[..$]" syntax is also present in ColdC and its relatives 
 (including my Bovis), so it was familiar to me from the beginning.  
 (That said I still harbor thoughts that $ could be used for other 
 things... but honestly, I think the syntax would be unambiguous: a 
 lone $ as the right hand side of a slice expression should easily 
 enough be distinguishable from a $ anywhere followed by something, 
 like an identifier.)
FWIW $ is not only used for the RHS of a slice char[] str; str[$/2..$]; // 2nd half of array str[$-1]; // last element in array str[$-5..$]; // last 5 things in array str[$-10..10]; // um... well... you get the idea
Ack. Having never used anything quite like that before, I guess I had assumed the $ only had meaning as I described above. Still, it could be possible.
 Which leads me to another thought.  One other operator that ColdC and 
 family posesses is the   for list splicing.  Useless sample ColdC:







 The 'result' variable now equals {1, 2, 3, 4, 5, 6}.
I would think this would be the same thing. auto foo = [1,2,3]; auto bar = [4,5,6]; auto result = foo ~ bar; am I missing somethign?
Not really, no. But consider: It becomes part of the literal syntax, which makes things cleaner in most elaborate cases. Just something I enjoy over there that I wouldn't mind seeing from time to time over here. :) -- Chris Nicholson-Sauls
Dec 20 2006
next sibling parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Chris Nicholson-Sauls wrote:
 BCS wrote:
 Chris Nicholson-Sauls wrote:
 The "[..$]" syntax is also present in ColdC and its relatives 
 (including my Bovis), so it was familiar to me from the beginning.  
 (That said I still harbor thoughts that $ could be used for other 
 things... but honestly, I think the syntax would be unambiguous: a 
 lone $ as the right hand side of a slice expression should easily 
 enough be distinguishable from a $ anywhere followed by something, 
 like an identifier.)
FWIW $ is not only used for the RHS of a slice char[] str; str[$/2..$]; // 2nd half of array str[$-1]; // last element in array str[$-5..$]; // last 5 things in array str[$-10..10]; // um... well... you get the idea
Ack. Having never used anything quite like that before, I guess I had assumed the $ only had meaning as I described above. Still, it could be possible.
These are very possible and are at times very useful. Though the last example is perhaps pushing it in the latter department ;). But yeah, $ is valid inside any [] pair, whether as (part of) an index or as (part of) either side of a slice.
 Which leads me to another thought.  One other operator that ColdC and 
 family posesses is the   for list splicing.  Useless sample ColdC:







 The 'result' variable now equals {1, 2, 3, 4, 5, 6}.
I would think this would be the same thing. auto foo = [1,2,3]; auto bar = [4,5,6]; auto result = foo ~ bar; am I missing somethign?
Not really, no. But consider:
You don't need to surround a non-array by [] if concatenating with an array of the same type: result = foo ~ 0 ~ bar; will work just fine.

result = 42 ~ someFunc();

result = foo ~ 1 ~ foo ~ 2;

result = [3, 6] ~ myConst ~ 9; The first []s are still needed, unless you add parentheses to group the 6 to myConst (and possibly the 9), but the last ones are unnecessary. Also, .dup is completely useless here (presuming myConst is an array) since ~ always allocates. (Note that repeated ~s in the same expression only allocate once though, at least in DMD)
Dec 20 2006
prev sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Chris Nicholson-Sauls wrote:
 BCS wrote:
 Chris Nicholson-Sauls wrote:
Not really, no. But consider: It becomes part of the literal syntax, which makes things cleaner in most elaborate cases. Just something I enjoy over there that I wouldn't mind seeing from time to time over here. :)
I think that syntax is a little more attractive than ~ for some cases. It makes it clearer that you're building a list. We don't say [] ~ 1 ~ 2 ~ 3 to make the array [1,2,3], after all. But for that reason (because it's generalizing array literals) I think it should use [] instead of {}. So result = [ foo, 0, bar]; To me that certainly does make it clearer that I'm making a list out of lists. The as a special symbol just for this kind of bothers me, though. And how would it work for user-defined types? I guess it could just be turned into the equivalent opCat calls... --bb
Dec 20 2006
prev sibling parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Derek Parnell wrote:
 On Wed, 20 Dec 2006 06:24:28 -0800, Andrei Alexandrescu (See Website For
 Email) wrote:
 
  
 A small book could be written on just how bad language design is using 
 "length" and "$" to capture slice size inside a slice expression. I 
 managed to write two lengthy emails to Walter about them, and just 
 barely got started. 
Please share your thoughts here if you can too.
Gladly; I dug my email and let me share a couple of excerpts. --------- int length = 5; int[] a = new int[length * 2]; int[] b = a[length .. length * 2]; int c = a[length - 1 .. (b[0 .. length])[0]); In each of its uses, length has a different semantics. The behavior is well-defined for all cases, but nonintuitive and about as pleasant as nails on the blackboard. Now D has a compile-time option to ban the "length" name in scopes in which the slice operator is used. That would render the example above illegal. There is also a rule that identifiers in nested scopes cannot mask one another. So length will be banned from *any* scope that nests a scope using a slice: int length; if (a) { foreach (b; c) { while (d) { switch (e) { case f: g = h[0 .. length - 1]; ... } } } } This code will not compile. Worse, it *will* compile until you add the slice operation. Combining the two rules and taking them to their logical conclusion, any code using "length" is frail because there's always a risk that somebody might insert a slice, rendering the entire function uncompilable. What happened is that now "length" has become a backdoor-introduced keyword. Books will advise users to never use it even when it works, coding standards will ban it, language lawyers will use it to detract D, and users of other languages will smile condescendingly and stay with their languages. There are a few ways out of it. "length" could be actually made a keyword. But even that one isn't very uniform, and steals yet another good identifier name. Another way out of it is to ban "length" but stick with "$". But "$" has another bunch of problems. It's a special character used only once, and only in a very particular situation. There is no general concept standing behind its usage: it sticks out like a sore thumb. "$" isn't the last index in an array. It's that only when used inside a slice, and refers only to the innermost index of the array. Quite a waste of a special character out there, and to little usefulness. But if we made "$" into an operator identifying the last element of _any_ array, which could refer to the last element of _the left-hand side_ array if we so want, then all of a sudden it becomes useful in a myriad of situations: int i = a[$ - 1]; // get last element int i = a[$b - 1]; // get a's element at position b.length - 1 if (a[$ - 1] == x) { ... } if ($a > 0) { ... } if ($a == $b) { ... } swap(a[0], a[$ - 1]); // swap first and last element --------------- Grammar for nullary/unary $: --------------- I think I nailed down the way the count operator $ can work in a manner that's terse, expressive, and safe. My basic goal is to enable the operator $ to be unary (applying to an array) to return its size, and also nullary (applying to nothing) to implicitly mean "fetch the size of the innermost array in the expression". So this code should work: int[] foo; foo[$ - 1]; // refers to foo's last element foo[$foo - 1]; // same int[][] bar; bar[foo[$]]; // refers to bar indexed with foo's last element bar[foo[$bar]]; // refers to bar indexed with foo's element at $bar To insert my operator $ within D's grammar, go to the grammar page: http://www.digitalmars.com/d/expression.html$UnaryExpression and scroll down to Unary Expression. There, add the following rules: UnaryExpression: PostfixExpression & UnaryExpression ... etc. etc. ... $ Identifier $ PostfixExpression . Identifier $ PostfixExpression ( ) $ PostfixExpression ( ArgumentList ) $ IndexExpression $ SliceExpression $ ArrayLiteral $ ( Expression ) Now a unary expression can be the $ operator followed by an identifier, a member access, a function call, an array access, or a slice expression (awesome! pick the size of the slice!), a literal array (for conformity), or a parenthesized expression. Perfect! But we haven't yet filled the role of $ as a nullary operator. To do so, let's go in the grammar to http://www.digitalmars.com/d/expression.html$PrimaryExpression and append one more rule to it the PrimaryExpression rule: PrimaryExpression: Identifier .Identifier ... etc. etc. ... $ Now the grammar is unambiguous and will properly distinguish unary and nullary uses of the $ operator. This is more elegant than the current crap with "$" and "length" popping up. Besides, you can now use $ in many more places than inside []s. However, the grammar size does increase quite a bit, which is more fuss than I hoped for just one operator. A simpler grammar would have been to simply allow: UnaryExpression: PostfixExpression & UnaryExpression ... etc. etc. ... $ PostfixExpression But this would have been ambiguous. If the compiler sees "$-1", then the bad grammar says that's a unary use of $ because -1 is a PostfixExpression. But that's not what we wanted! We wanted $ to be nullary. That's why I needed to put all the cases in UnaryExpression. Andrei
Dec 21 2006
next sibling parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Derek Parnell wrote:
 On Wed, 20 Dec 2006 06:24:28 -0800, Andrei Alexandrescu (See Website For
 Email) wrote:

  
 A small book could be written on just how bad language design is 
 using "length" and "$" to capture slice size inside a slice 
 expression. I managed to write two lengthy emails to Walter about 
 them, and just barely got started. 
Please share your thoughts here if you can too.
Gladly; I dug my email and let me share a couple of excerpts.
<snipped excerpts> Wow, I understand it now. I only hope that at least 'length' will be deprecated before 1.0. I like your dollars. I'm not so good with grammars, will your proposal also work for user defined types?
Dec 21 2006
parent "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Lutger wrote:
 Wow, I understand it now. I only hope that at least 'length' will be 
 deprecated before 1.0.
 
 I like your dollars.
Well, just don't take'em away from my bank account :o).
 I'm not so good with grammars, will your proposal 
 also work for user defined types?
The plan is that $expression is rewritten into (expression).length. The consistent thing to do is to make that into an onXyz() function, but I don't find this name inconsistency jarring. Andrei
Dec 21 2006
prev sibling next sibling parent reply Pragma <ericanderton yahoo.removeme.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 
 A simpler grammar would have been to simply allow:
 
 UnaryExpression:
     PostfixExpression
     & UnaryExpression
     ... etc. etc. ...
     $ PostfixExpression
 
 But this would have been ambiguous. If the compiler sees "$-1", then the 
 bad grammar says that's a unary use of $ because -1 is a 
 PostfixExpression. But that's not what we wanted! We wanted $ to be 
 nullary. That's why I needed to put all the cases in UnaryExpression.
 
Nice post, and one heck of an argument! FWIW, I advocated something similar during the last round of debates before the '$' operator was introduced. What I wanted to see was '$' to become like 'this' within slice and array expressions, so that the issues regarding 'length' could be resolved. In essence one could simply say '$.length' and mean 'the length of the current array': b[0 .. $.length]; a[0 .. $.getIndexOf(';')]; So in essence, every use of '$' would be a 'nullary' operator - an alias if you will. I'd imagine that extending things in this manner would simplify things grammatically while allowing for a wider category of uses. However, it doesn't solve the issue that you brought up, and that I've quoted above. c[$-1]; It looks like it should be an implicit cast of the '$' to a size_t (length), via it's use in an expression. Any thoughts on this? -- - EricAnderton at yahoo
Dec 21 2006
next sibling parent reply Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Pragma wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 A simpler grammar would have been to simply allow:

 UnaryExpression:
     PostfixExpression
     & UnaryExpression
     ... etc. etc. ...
     $ PostfixExpression

 But this would have been ambiguous. If the compiler sees "$-1", then 
 the bad grammar says that's a unary use of $ because -1 is a 
 PostfixExpression. But that's not what we wanted! We wanted $ to be 
 nullary. That's why I needed to put all the cases in UnaryExpression.
Nice post, and one heck of an argument! FWIW, I advocated something similar during the last round of debates before the '$' operator was introduced. What I wanted to see was '$' to become like 'this' within slice and array expressions, so that the issues regarding 'length' could be resolved. In essence one could simply say '$.length' and mean 'the length of the current array': b[0 .. $.length]; a[0 .. $.getIndexOf(';')]; So in essence, every use of '$' would be a 'nullary' operator - an alias if you will.
I rather like this. And I think I liked it then, too... if not, oh well.
 I'd imagine that extending things in this manner would simplify things 
 grammatically while allowing for a wider category of uses.  However, it 
 doesn't solve the issue that you brought up, and that I've quoted above.
 
 c[$-1];
 
 It looks like it should be an implicit cast of the '$' to a size_t 
 (length), via it's use in an expression.  Any thoughts on this?
If $ is like a 'this', then it ought to be have semantically the same, so if $ is a class/struct with an opCast to size_t defined, the obvious happens. If its anything else, it ought to be a compile time error, perhaps suggesting you had meant '$.length' instead. -- Chris Nicholson-Sauls
Dec 21 2006
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Chris Nicholson-Sauls wrote:
 Pragma wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 A simpler grammar would have been to simply allow:

 UnaryExpression:
     PostfixExpression
     & UnaryExpression
     ... etc. etc. ...
     $ PostfixExpression

 But this would have been ambiguous. If the compiler sees "$-1", then 
 the bad grammar says that's a unary use of $ because -1 is a 
 PostfixExpression. But that's not what we wanted! We wanted $ to be 
 nullary. That's why I needed to put all the cases in UnaryExpression.
Nice post, and one heck of an argument! FWIW, I advocated something similar during the last round of debates before the '$' operator was introduced. What I wanted to see was '$' to become like 'this' within slice and array expressions, so that the issues regarding 'length' could be resolved. In essence one could simply say '$.length' and mean 'the length of the current array': b[0 .. $.length]; a[0 .. $.getIndexOf(';')]; So in essence, every use of '$' would be a 'nullary' operator - an alias if you will.
In both of those cases the use seems rather silly to me because a and b are both single characters to begin with. Might as well just type b[0 .. b.length]; a[0 .. a.getIndexOf(';')]; instead. But I get the point. Sometimes you have g_openSocketHandles[0 .. g_openSocketHandles.getIndexOf()] But maybe just allowing 'this' in the brackets is enough there, without going on and abbreviating it to $. The $==.length proposal at least has the advantage of being backwards compatible.

 
 I rather like this.  And I think I liked it then, too... if not, oh well.
 
 I'd imagine that extending things in this manner would simplify things 
 grammatically while allowing for a wider category of uses.  However, 
 it doesn't solve the issue that you brought up, and that I've quoted 
 above.

 c[$-1];

 It looks like it should be an implicit cast of the '$' to a size_t 
 (length), via it's use in an expression.  Any thoughts on this?
If $ is like a 'this', then it ought to be have semantically the same, so if $ is a class/struct with an opCast to size_t defined, the obvious happens. If its anything else, it ought to be a compile time error, perhaps suggesting you had meant '$.length' instead. -- Chris Nicholson-Sauls
Not sure I like $==this as must as $==.length. I have pressing need for a brief syntax for specifying the length, but no such thing for a shorter form of 'this'. But anyway, if you're going to allow '$' to mean 'this' inside brackets, first you first need the language feature that allows 'this' to be used inside brackets in the first place. And maybe if you have that you'll find it's sufficient. Another thing is if you're going to allow 'this' in brackets, then you should take the idea to its logical conclusions and allow it in member function call parameter lists too. That might be nice for things like enum paramters. Of course if $ gets translated into a call to a method/property, you could have it your way if you prefer for your classes. Just use opDollar() { return this; } and voila! You can use your $.getIndexOf(';'). --bb
Dec 21 2006
parent Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Bill Baxter wrote:
 Chris Nicholson-Sauls wrote:
 Pragma wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 A simpler grammar would have been to simply allow:

 UnaryExpression:
     PostfixExpression
     & UnaryExpression
     ... etc. etc. ...
     $ PostfixExpression

 But this would have been ambiguous. If the compiler sees "$-1", then 
 the bad grammar says that's a unary use of $ because -1 is a 
 PostfixExpression. But that's not what we wanted! We wanted $ to be 
 nullary. That's why I needed to put all the cases in UnaryExpression.
Nice post, and one heck of an argument! FWIW, I advocated something similar during the last round of debates before the '$' operator was introduced. What I wanted to see was '$' to become like 'this' within slice and array expressions, so that the issues regarding 'length' could be resolved. In essence one could simply say '$.length' and mean 'the length of the current array': b[0 .. $.length]; a[0 .. $.getIndexOf(';')]; So in essence, every use of '$' would be a 'nullary' operator - an alias if you will.
In both of those cases the use seems rather silly to me because a and b are both single characters to begin with. Might as well just type b[0 .. b.length]; a[0 .. a.getIndexOf(';')]; instead. But I get the point. Sometimes you have g_openSocketHandles[0 .. g_openSocketHandles.getIndexOf()] But maybe just allowing 'this' in the brackets is enough there, without going on and abbreviating it to $. The $==.length proposal at least has the advantage of being backwards compatible.

 I rather like this.  And I think I liked it then, too... if not, oh well.

 I'd imagine that extending things in this manner would simplify 
 things grammatically while allowing for a wider category of uses.  
 However, it doesn't solve the issue that you brought up, and that 
 I've quoted above.

 c[$-1];

 It looks like it should be an implicit cast of the '$' to a size_t 
 (length), via it's use in an expression.  Any thoughts on this?
If $ is like a 'this', then it ought to be have semantically the same, so if $ is a class/struct with an opCast to size_t defined, the obvious happens. If its anything else, it ought to be a compile time error, perhaps suggesting you had meant '$.length' instead. -- Chris Nicholson-Sauls
Not sure I like $==this as must as $==.length. I have pressing need for a brief syntax for specifying the length, but no such thing for a shorter form of 'this'. But anyway, if you're going to allow '$' to mean 'this' inside brackets, first you first need the language feature that allows 'this' to be used inside brackets in the first place. And maybe if you have that you'll find it's sufficient.
The problem with actually using the 'this' keyword in place of $ is one of ambiguity. Given a collection class 'Set' and some other class 'Foo', what to do if a 'this' is used within a slice of a 'Set' instance within a member of 'Foo'? Does it evaluate to the Foo referance it would in all other cases? Or to a Set referanc? And if the latter, how to get the Foo referance if that really is what I wanted? The $ would have to be different from 'this' in the classes' sense. Perhaps it would be better to call it a 'self' or even a 'with' than a 'this'. -- Chris Nicholson-Sauls
Dec 21 2006
prev sibling parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Pragma wrote:
 b[0 .. $.length];
 a[0 .. $.getIndexOf(';')];
 
 So in essence, every use of '$' would be a 'nullary' operator - an alias 
 if you will.
This isn't going to be agreeable to most since the purpose of $ in the first place was to save typing.
 I'd imagine that extending things in this manner would simplify things 
 grammatically while allowing for a wider category of uses.  However, it 
 doesn't solve the issue that you brought up, and that I've quoted above.
 
 c[$-1];
 
 It looks like it should be an implicit cast of the '$' to a size_t 
 (length), via it's use in an expression.  Any thoughts on this?
I'd rather have $ defined everywhere to mean length, which is useful outside [] as well. Andrei P.S. Maybe there's a misunderstanding? The grammar I sent does not have a problem w.r.t. unary vs. nullary; it's just a tad more complicated to avoid ambiguity.
Dec 22 2006
parent Pragma <ericanderton yahoo.removeme.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Pragma wrote:
 b[0 .. $.length];
 a[0 .. $.getIndexOf(';')];

 So in essence, every use of '$' would be a 'nullary' operator - an 
 alias if you will.
This isn't going to be agreeable to most since the purpose of $ in the first place was to save typing.
 I'd imagine that extending things in this manner would simplify things 
 grammatically while allowing for a wider category of uses.  However, 
 it doesn't solve the issue that you brought up, and that I've quoted 
 above.

 c[$-1];

 It looks like it should be an implicit cast of the '$' to a size_t 
 (length), via it's use in an expression.  Any thoughts on this?
I'd rather have $ defined everywhere to mean length, which is useful outside [] as well.
Understood. I just figured I'd throw that out there in case it had any merit in the current discussion.
 
 Andrei
 
 P.S. Maybe there's a misunderstanding? The grammar I sent does not have 
 a problem w.r.t. unary vs. nullary; it's just a tad more complicated to 
 avoid ambiguity.
Ah, I understand then. The way you explained the grammar changes, it looked to me as though there was still an issue. -- - EricAnderton at yahoo
Dec 22 2006
prev sibling next sibling parent reply Don Clugston <dac nospam.com.au> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Another way out of it is to ban "length" but stick with "$". But "$" has 
 another bunch of problems. It's a special character used only once, and 
 only in a very particular situation. There is no general concept 
 standing behind its usage: it sticks out like a sore thumb. "$" isn't 
 the last index in an array. It's that only when used inside a slice, and 
 refers only to the innermost index of the array. Quite a waste of a 
 special character out there, and to little usefulness.
 
 But if we made "$" into an operator identifying the last element of 
 _any_ array, which could refer to the last element of _the left-hand 
 side_ array if we so want, then all of a sudden it becomes useful in a 
 myriad of situations:
Provided that some such expansion path for "$" exists, it would seem to be adequate for D 1.0 to just remove "length". And this could be done by Jan 1.
Dec 21 2006
parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Don Clugston wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Another way out of it is to ban "length" but stick with "$". But "$" 
 has another bunch of problems. It's a special character used only 
 once, and only in a very particular situation. There is no general 
 concept standing behind its usage: it sticks out like a sore thumb. 
 "$" isn't the last index in an array. It's that only when used inside 
 a slice, and refers only to the innermost index of the array. Quite a 
 waste of a special character out there, and to little usefulness.

 But if we made "$" into an operator identifying the last element of 
 _any_ array, which could refer to the last element of _the left-hand 
 side_ array if we so want, then all of a sudden it becomes useful in a 
 myriad of situations:
Provided that some such expansion path for "$" exists, it would seem to be adequate for D 1.0 to just remove "length". And this could be done by Jan 1.
That is correct. One advantage of the unary/nullary $ is that it's a strict extension to today's $. Thus, no existing code will be invalidated by the new semantics of $. Andrei
Dec 22 2006
prev sibling next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:

 But if we made "$" into an operator identifying the last element of 
 _any_ array, which could refer to the last element of _the left-hand 
 side_ array if we so want, then all of a sudden it becomes useful in a 
 myriad of situations:
 
 int i = a[$ - 1]; // get last element
 int i = a[$b - 1]; // get a's element at position b.length - 1
 if (a[$ - 1] == x) { ... }
 if ($a > 0) { ... }
 if ($a == $b) { ... }
 swap(a[0], a[$ - 1]); // swap first and last element
Please give some thought to the case where a and b are of types not easily characterized by a single '.length'. Matrix classes, or more generally multidimensional array classes being the canonical examples. For those cases it is desirable to be able to have a '$' with different meaning "per axis". For those cases a we could have a small extension to your proposal. Have $b translate to b.length, yes, but also have $[3]b and $(1)b translate to to b.length[3] and b.length(1), respectively. Seeing that, it makes me think perhaps $ would be better as a post-fix unary operator. Then we'd have b$ --> b.length and b$[3] --> b.length[3]. Then of course the next step is to have a parameter number automatically passed to the length method given and expression like a[$-1,$-1] so that a[$-1,$-1] ==> a[$[0]-1,$[1]-1] ==> a[a$[0]-1,a$[1]-1] ==> a[a.length[0],a.length[1]] The compiler can decide whether to do indexing or not based on whether .length results in an indexable value. Finally, in general I think the choice of name 'length' is unfortunate because of it's implication of linearity. But it's not too late. If $ becomes associated with .size rather than .length in user types then everything will be ok. For built-in arrays .length can become a synonym for .size, just as it is with std::string in C++. C++/STL got this one right. For generic containers .size is a much better name. --bb
Dec 22 2006
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Bill Baxter wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 
 Then of course the next step is to have a parameter number automatically 
 passed to the length method given and expression like a[$-1,$-1] so that
     a[$-1,$-1]
     ==>  a[$[0]-1,$[1]-1]
     ==>  a[a$[0]-1,a$[1]-1]
     ==>  a[a.length[0],a.length[1]]
Slight typo there. Last line should of course have been: ==> a[a.length[0]-1,a.length[1]-1]
 The compiler can decide whether to do indexing or not based on whether 
 .length results in an indexable value.
 
 Finally, in general I think the choice of name 'length' is unfortunate 
 because of it's implication of linearity.  But it's not too late.  If $ 
 becomes associated with .size rather than .length in user types then 
 everything will be ok.  For built-in arrays .length can become a synonym 
 for .size, just as it is with std::string in C++.  C++/STL got this one 
 right.  For generic containers .size is a much better name.
Another thing which occurred to me is that if the meaning of $ becomes tied to "size" rather than "length", then then you also have the mnemonic of $ looking like an 's' as in 'size'. I also still think making it a postfix operator makes sense. --bb
Dec 21 2006
prev sibling parent reply "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Bill Baxter wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 
 But if we made "$" into an operator identifying the last element of 
 _any_ array, which could refer to the last element of _the left-hand 
 side_ array if we so want, then all of a sudden it becomes useful in a 
 myriad of situations:

 int i = a[$ - 1]; // get last element
 int i = a[$b - 1]; // get a's element at position b.length - 1
 if (a[$ - 1] == x) { ... }
 if ($a > 0) { ... }
 if ($a == $b) { ... }
 swap(a[0], a[$ - 1]); // swap first and last element
Please give some thought to the case where a and b are of types not easily characterized by a single '.length'. Matrix classes, or more generally multidimensional array classes being the canonical examples. For those cases it is desirable to be able to have a '$' with different meaning "per axis".
I did. The thing with language design is that it's easy to either underdo or overdo it, and that where underdoing or overdoing starts is highly subjective. IMHO the current meaning of "$" is a good example of underdoing. The "$expression" meaning "(expression).length" is (again IMHO) just right. I use collection.size() all the time in C++, and scalar( array) or $#array all the time in Perl, inside and outside index expressions. So I'd be happy to have that. Taking it to the next step of meaning any subdimension of a multidimensional (or fractal, heh) structure is, IMHO, overdoing because I can think of few use examples that are both frequent enough and interesting enough. Andrei
Dec 22 2006
parent Bill Baxter <wbaxter gmail.com> writes:
Andrei Alexandrescu (See Website for Email) wrote:
 Bill Baxter wrote:
 
 Andrei Alexandrescu (See Website For Email) wrote:

 But if we made "$" into an operator identifying the last element of 
 _any_ array, which could refer to the last element of _the left-hand 
 side_ array if we so want, then all of a sudden it becomes useful in 
 a myriad of situations:

 int i = a[$ - 1]; // get last element
 int i = a[$b - 1]; // get a's element at position b.length - 1
 if (a[$ - 1] == x) { ... }
 if ($a > 0) { ... }
 if ($a == $b) { ... }
 swap(a[0], a[$ - 1]); // swap first and last element
Please give some thought to the case where a and b are of types not easily characterized by a single '.length'. Matrix classes, or more generally multidimensional array classes being the canonical examples. For those cases it is desirable to be able to have a '$' with different meaning "per axis".
I did. The thing with language design is that it's easy to either underdo or overdo it, and that where underdoing or overdoing starts is highly subjective. IMHO the current meaning of "$" is a good example of underdoing. The "$expression" meaning "(expression).length" is (again IMHO) just right. I use collection.size() all the time in C++, and scalar( array) or $#array all the time in Perl, inside and outside index expressions. So I'd be happy to have that. Taking it to the next step of meaning any subdimension of a multidimensional (or fractal, heh) structure is, IMHO, overdoing because I can think of few use examples that are both frequent enough and interesting enough.
Maybe so. Multidimensional arrays seem as common as air from where I sit, but I can see that not everyone works with such things every day. If $ really did become synonymous with .length (or preferably .size) then one could have .length return an array rather than a simple number. In that case multi-dim'ers could have M[5..$[0]-1, 0..$[1]-5]. Eh. Not so pretty. For comparison, in Python that would be M[5:,:-5], and in Matlab that would be M(6:end, 1:end-4). Both of those look much better to me. If the index to go with $ could be supplied automatically by the compiler then D could have M[5..$-1, 0..$-5]. Taking a different tack, I wonder if repeated indexing can be made as efficient (or nearly) as a single multi-index? M[5..$-1][0..$-5] That's much easier to look at than M[5..$[0]-1, 0..$[1]-5], at least. And it's more general in the sense that from looking at the expression, M looks just like a standard Type[][] array. Hmm. I'll play with that. I think it's at least technically possible, now that D has the ability to override opAssign. --bb
Dec 22 2006
prev sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 A simpler grammar would have been to simply allow:
 
 UnaryExpression:
     PostfixExpression
     & UnaryExpression
     ... etc. etc. ...
     $ PostfixExpression
 
 But this would have been ambiguous. If the compiler sees "$-1", then the 
 bad grammar says that's a unary use of $ because -1 is a 
 PostfixExpression. But that's not what we wanted! We wanted $ to be 
 nullary. That's why I needed to put all the cases in UnaryExpression.
Wouldn't that ambiguity be fixed by making $ a postfix unary operator instead? Or would that just introduce different ambiguities? --bb
Dec 24 2006
prev sibling next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Don Clugston wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Similarly, let's say that a group of revolutionaries convinces Walter 
 (as I understand happened in case of using "length" and "$" inside 
 slice expressions, which is a shame and an absolute disaster that 
 must be undone at all costs) to implement "auto"
This off-hand remark worries me. I presume that you mean being able to reference the length of a string, from inside the slice? (rather than simply the notation). And the problem being that it requires a sliceable entity to know its length? Or is the problem more serious than that? It's worrying because any change would break an enormous amount of code.
It would indeed break an enormous amount of code, but "all costs" includes "enormous costs". :o) A reasonable migration path is to deprecate them soon and make them illegal over the course of one year. A small book could be written on just how bad language design is using "length" and "$" to capture slice size inside a slice expression. I managed to write two lengthy emails to Walter about them, and just barely got started. Long story short, "length" introduces a keyword through the back door, effectively making any use of "length" anywhere unrecommended and highly fragile.
That hadn't occurred to me, but you're right. I never use length in that context precisely because it does look like it could be a local identifier, whereas I know it'll be clear it's not if I use $. Also "length" is just too long to be of much use to me as a shortcut. If I'm going to be that verbose I might as well type out the whole "varname.length".
 Using "$" is a waste of symbolic real 
 estate to serve a narrow purpose; the semantics isn't naturally 
 generalized to its logical conclusion; 
I do use this one, but I agree. It is unnecessarily special cased for built-in array types. For user-defined types, in 'myvar[0..$]' the $ does not expand to 'myvar.length' as one would naturally expect it to. Or any sort of opLength() call. It's just a syntax error.
 and the choice of symbol itself 

 been vastly better as it has count connotation in natural language, and 
 making it into an operator would have fixed the generalization issue). 
I think you'll have to admit that's just your personal taste there. Using $ to indicate 'end' is a regexp thing, but regexp's go way beyond Perl. I don't really care what it is as long as there's an terse way to specify 'the end' in an indexing expression.
 As things stand now, the rules governing the popping up of "length" and 
 "$" constitute a sudden boo-boo on an otherwise carefully designed 
 expression landscape.
After trying to write a multi-dimensional array class, my opinion is that D slice support could use some upgrades overall. What I'd like to see: --MultiRange Slice-- * A way to have multiple ranges in a slice, and a mix slice of and non-slice indices: A[i..j, k..m] A[i..j, p, k..m] I'm not saying built-in arrays like int[] should allow the above expressions, but that at least user types should be allowed to have such opSlice methods. (Currently opSlice's are limited to having 2 arguments that represent the values that appear on either side of a single '..' token. You can only have two arguments max, but the arguments can be of any type.) The problem is that opSlice has to look like opSlice(T1 lo, T2 hi) right now -- just two parameters (or zero). One possible solution is to turn a single i..j into a single int[2] argument (or a mytype[2], for the general case). But that means one won't be able to distinguish A[[1,3]] from A[1..3]. It also means more interesting extensions to slice syntax, like adding a stepsize on a range, will be ruled out. Another solution is a built-in slice type. Ranges like a..b would get converted to slice instances automatically. It would basically be a struct with two ints in the simplest case, but to support user types as indexes it would need to be template-like, i.e. slice!(type). A slice would look basically like struct slice(T=int) { T lo,hi; } It could also have a .step property. With the above, lo and hi would have to be of the same type, but really it makes sense to let them differ, so slice!(T1,T2). For a range with stepsize, slice!(Tlo,Thi,Tstep). To make writing opSlice methods sane, a single number like the p above should be converted to a slice also. So all arguments passed to opSlice would be of type slice, and in the simple case of integer indices, it would just be: Type opSlice(slice s) { return x[s.lo..s.hi]; } since integers would be the default types for slice. --User Definable '$'-- * A way to specify 'the end' in user types. In the general case the meaning of '$' in a slice cannot be known (because any type can be used as an index), nor can it be simply substituted with something like a .length property, because it may depend on context. Consider a multi-dimensional array class -- A[0..$,3..$] The first $ means one thing, and the second one means another. One solution - make an opLength that gets called with the parameter only context that ever matters in determining the meaning of $.] So in the above int opLength(int i) would get called twice, once with i==0, once with i==1. opLength can be made to return any type if the user just wants it to get 'passed through' to the opSlice call. If you don't need the context you can define it as opLength(). --Step sizes-- This is a handy feature of Python slices. The general syntax for a slice in Python is lo:hi:step, meaning go from 'lo' to 'hi', stepping by 'step' at a time. But any of the 3 components can be left out. lo:hi means step=1. lo::2 means go to the end, stepping by 2. :hi means 0 to hi. Negative steps are also allowed: hi:lo:-1 means go backwards from hi to lo ::-1 go backwards from the last to first element D syntax could be something like lo..hi:step. I like the omission part of Python's syntax. If D had that then most uses of $ would go away since we'd have A[3..] as an alternative to A[3..$]. --bb
Dec 20 2006
parent reply Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
Bill Baxter wrote:
 After trying to write a multi-dimensional array class, my opinion is 
 that D slice support could use some upgrades overall.
I'd be very interested in looking at what you've come up with. With my own implementation of a multi-dimensional array type a couple of months ago, I came to the same conclusion. I posted about it in: news://news.digitalmars.com:119/edrv0n$hth$1 digitaldaemon.com http://www.digitalmars.com/d/archives/digitalmars/D/announce/4717.html
 What I'd like to see:
 
 --MultiRange Slice--
 * A way to have multiple ranges in a slice, and a mix slice of and 
 non-slice indices:
     A[i..j, k..m]
     A[i..j, p, k..m]
(snip)
      A[0..$,3..$]
Yes, I would too. It is quite frustrating having the syntax in the language but not being allowed to utilize it... :) I work around this by instead using a custom slice syntax instead: A[range(i,j), range(k,m)] A[range(i,j), p, range(k,m)] A[range(0,end), range(3..end)] A[end-1, p % end] Basicly, the transformation is: $ => end a..b => range(a,b) I briefly described this in: news://news.digitalmars.com:119/eft9id$2aq3$1 digitaldaemon.com The resulting code becomes quite optimal without the need for a position dependent opLength type of operator, but handling all the cases puts a larger burden on the implementor of opIndex.
 The problem is that opSlice has to look like opSlice(T1 lo, T2 hi) right 
 now -- just two parameters (or zero).
[snip]
 Another solution is a built-in slice type.  Ranges like a..b would get 
 converted to slice instances automatically.  
Yes, this would be my suggestion too. Adding an opApply to one such built in range type would also have the nice side effect of allowing the syntactical sugar: foreach(i; 5..10)
 --User Definable '$'--
[snip]
 One solution - make an opLength that gets called with the parameter 
 number in which the $ appears. 
Yes, that is probably the cleanest solution. And if no such opLength(int) overload exists, return the result of opLength() (or possibly .length) /Oskar
Dec 21 2006
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Oskar Linde wrote:
 Bill Baxter wrote:
 After trying to write a multi-dimensional array class, my opinion is 
 that D slice support could use some upgrades overall.
I'd be very interested in looking at what you've come up with. With my own implementation of a multi-dimensional array type a couple of months ago, I came to the same conclusion. I posted about it in: news://news.digitalmars.com:119/edrv0n$hth$1 digitaldaemon.com http://www.digitalmars.com/d/archives/digitalmars/D/announce/4717.html
 What I'd like to see:

 --MultiRange Slice--
 * A way to have multiple ranges in a slice, and a mix slice of and 
 non-slice indices:
     A[i..j, k..m]
     A[i..j, p, k..m]
(snip) > A[0..$,3..$] Yes, I would too. It is quite frustrating having the syntax in the language but not being allowed to utilize it... :) I work around this by instead using a custom slice syntax instead: A[range(i,j), range(k,m)] A[range(i,j), p, range(k,m)] A[range(0,end), range(3..end)] A[end-1, p % end]
Yeh, that's similar to what I'm doing too. But it's pretty ugly. So I guess that means you're using opIndex for everything and leaving opSlice alone. Are you able to have ranges return arrays and specific indexes return scalar values that way? That seems to me a big reason for having opSlice exist in the first place. The .. in the brackets not only means you're slicing, it also means the function should return another array, versus returning an element. That seems like a nice distinction to have to me.
 
 Basicly, the transformation is:
 
 $ => end
 a..b => range(a,b)
 
 I briefly described this in:
 news://news.digitalmars.com:119/eft9id$2aq3$1 digitaldaemon.com
Thanks for the link. The 'end' thing isn't so bad, at least for a former Matlab user. :-) --bb
Dec 24 2006
next sibling parent reply Reiner Pope <xxxxxx xxx.xx> writes:
Bill Baxter wrote:
 Oskar Linde wrote:
 Bill Baxter wrote:
 After trying to write a multi-dimensional array class, my opinion is 
 that D slice support could use some upgrades overall.
I'd be very interested in looking at what you've come up with. With my own implementation of a multi-dimensional array type a couple of months ago, I came to the same conclusion. I posted about it in: news://news.digitalmars.com:119/edrv0n$hth$1 digitaldaemon.com http://www.digitalmars.com/d/archives/digitalmars/D/announce/4717.html
 What I'd like to see:

 --MultiRange Slice--
 * A way to have multiple ranges in a slice, and a mix slice of and 
 non-slice indices:
     A[i..j, k..m]
     A[i..j, p, k..m]
(snip) > A[0..$,3..$] Yes, I would too. It is quite frustrating having the syntax in the language but not being allowed to utilize it... :) I work around this by instead using a custom slice syntax instead: A[range(i,j), range(k,m)] A[range(i,j), p, range(k,m)] A[range(0,end), range(3..end)] A[end-1, p % end]
Yeh, that's similar to what I'm doing too. But it's pretty ugly. So I guess that means you're using opIndex for everything and leaving opSlice alone. Are you able to have ranges return arrays and specific indexes return scalar values that way? That seems to me a big reason for having opSlice exist in the first place. The .. in the brackets not only means you're slicing, it also means the function should return another array, versus returning an element. That seems like a nice distinction to have to me.
Is there anything particularly wrong with having foo[a..b,c,d..$] being syntactical sugar for foo[a..b][c][d..$] ? That way, I would imagine you could implement slicing and indexing of multidimensional arrays quite easily because, as Norbert Nemec said, indexing just returns an array with dimension reduced by one, and slicing returns an array of the same dimension, but perhaps different size. It also seems to allow any combination of slicing and indexing without needing variadic functions.
Dec 24 2006
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Reiner Pope wrote:
 Bill Baxter wrote:
 Oskar Linde wrote:
 Bill Baxter wrote:
 After trying to write a multi-dimensional array class, my opinion is 
 that D slice support could use some upgrades overall.
I'd be very interested in looking at what you've come up with. With my own implementation of a multi-dimensional array type a couple of months ago, I came to the same conclusion. I posted about it in: news://news.digitalmars.com:119/edrv0n$hth$1 digitaldaemon.com http://www.digitalmars.com/d/archives/digitalmars/D/announce/4717.html
 What I'd like to see:

 --MultiRange Slice--
 * A way to have multiple ranges in a slice, and a mix slice of and 
 non-slice indices:
     A[i..j, k..m]
     A[i..j, p, k..m]
(snip) > A[0..$,3..$] Yes, I would too. It is quite frustrating having the syntax in the language but not being allowed to utilize it... :) I work around this by instead using a custom slice syntax instead: A[range(i,j), range(k,m)] A[range(i,j), p, range(k,m)] A[range(0,end), range(3..end)] A[end-1, p % end]
Yeh, that's similar to what I'm doing too. But it's pretty ugly. So I guess that means you're using opIndex for everything and leaving opSlice alone. Are you able to have ranges return arrays and specific indexes return scalar values that way? That seems to me a big reason for having opSlice exist in the first place. The .. in the brackets not only means you're slicing, it also means the function should return another array, versus returning an element. That seems like a nice distinction to have to me.
Is there anything particularly wrong with having foo[a..b,c,d..$] being syntactical sugar for foo[a..b][c][d..$] ? That way, I would imagine you could implement slicing and indexing of multidimensional arrays quite easily because, as Norbert Nemec said, indexing just returns an array with dimension reduced by one, and slicing returns an array of the same dimension, but perhaps different size. It also seems to allow any combination of slicing and indexing without needing variadic functions.
Generally speaking, indexing isn't free. And doing it three times is 3x more expensive than doing it once. At the very least if the thing being indexed is a class then a new instance of that class has to be created for each index op. But likely there's also some internal state that has to be copied and adjusted as well. And one expects that indexing operations will often appear in inner loops, so they should be as fast as possible. However, if you use some sort of proxy structs to represent the intermediate indexing expressions and only do the indexing when it's really needed, it may be ok to use [][][]. Basically it's expression templates all over again, just here the expressions are limited to indexing or slice operations. That may work, and it may even be as efficient as a real multi-index slice with compiler optimizations, but I think it will result in code that's far less clear and probably not as fast. If it does pan out, though, then there are certainly advantages as you say to only having to worry about two cases ever -- single index and slice index. Not the any-possible-combination-of-index-and-slice, which admittedly requires some slick vararg template trickery itself. Anyway, I plan to try implementing that soon (with all the brackets, naturally since the syntactic sugar you mention currently doesn't exist). --bb
Dec 24 2006
prev sibling parent Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
== Quote from Reiner Pope (xxxxxx xxx.xx)'s article
 Is there anything particularly wrong with having foo[a..b,c,d..$] being
 syntactical sugar for foo[a..b][c][d..$] ? That way, I would imagine you
 could implement slicing and indexing of multidimensional arrays quite
 easily because, as Norbert Nemec said, indexing just returns an array
 with dimension reduced by one, and slicing returns an array of the same
 dimension, but perhaps different size. It also seems to allow any
 combination of slicing and indexing without needing variadic functions.
It is a neat idea that unfortunately doesn't work. foo[a..b,c..d] means slice from a to b along dimension 1 and from c to d along dimension 2. foo[a..b] would therefore return a 2-dimensional slice (same dimensionality as foo), and (foo[a..b])[c..d] would slice the same dimension again. Unless, of course, foo[a..b] would return some kind of proxy object that kept track of the last sliced dimension. /Oskar
Dec 26 2006
prev sibling parent reply Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
== Quote from Bill Baxter (dnewsgroup billbaxter.com)'s article
 Oskar Linde wrote:
 Bill Baxter wrote:
 After trying to write a multi-dimensional array class, my opinion is
 that D slice support could use some upgrades overall.
I'd be very interested in looking at what you've come up with. With my own implementation of a multi-dimensional array type a couple of months ago, I came to the same conclusion. I posted about it in: news://news.digitalmars.com:119/edrv0n$hth$1 digitaldaemon.com http://www.digitalmars.com/d/archives/digitalmars/D/announce/4717.html
 What I'd like to see:
--MultiRange Slice--
 * A way to have multiple ranges in a slice, and a mix slice of and
 non-slice indices:
     A[i..j, k..m]
     A[i..j, p, k..m]
(snip)
     A[0..$,3..$]
Yes, I would too. It is quite frustrating having the syntax in the language but not being allowed to utilize it... :) I work around this by instead using a custom slice syntax instead: A[range(i,j), range(k,m)] A[range(i,j), p, range(k,m)] A[range(0,end), range(3..end)] A[end-1, p % end]
Yeh, that's similar to what I'm doing too. But it's pretty ugly. So I guess that means you're using opIndex for everything and leaving opSlice alone.
Yes. Apart from the no-argument opSlice and opSliceAssign, that I overload with the same meaning as for the built in arrays. opSlice is not designed to support more than one dimension.
 Are you able to have ranges return arrays and specific indexes
 return scalar values that way?
Yes.
 That seems to me a big reason for having
 opSlice exist in the first place.  The .. in the brackets not only means
 you're slicing, it also means the function should return another array,
 versus returning an element.  That seems like a nice distinction to have
 to me.
Yes, definitely. My implementation mimics the behavior of the D native array/slice type (T[]), but extended for multiple dimensions and strides. So given Array!(2,int) A, A[all,5] and A[5,all] are both Array!(1,int) of the 6-th row and column of A. A[5,5] is an int. Some other neat features made possible by strided arrays are for instance A.diag, that returns the 1-dimensional diagonal of the array, so eg: A[] = 0; A.diag[] = 1; would construct the unit matrix.
 Basicly, the transformation is:

 $ => end
 a..b => range(a,b)

 I briefly described this in:
 news://news.digitalmars.com:119/eft9id$2aq3$1 digitaldaemon.com
Thanks for the link. The 'end' thing isn't so bad, at least for a former Matlab user. :-)
I wish I too could call myself a _former_ such. :) /Oskar
Dec 26 2006
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Oskar Linde wrote:
 == Quote from Bill Baxter (dnewsgroup billbaxter.com)'s article
 Oskar Linde wrote:
 Bill Baxter wrote:
 After trying to write a multi-dimensional array class, my opinion is
 that D slice support could use some upgrades overall.
I'd be very interested in looking at what you've come up with. With my own implementation of a multi-dimensional array type a couple of months ago, I came to the same conclusion. I posted about it in: news://news.digitalmars.com:119/edrv0n$hth$1 digitaldaemon.com http://www.digitalmars.com/d/archives/digitalmars/D/announce/4717.html
 What I'd like to see:
--MultiRange Slice--
 * A way to have multiple ranges in a slice, and a mix slice of and
 non-slice indices:
     A[i..j, k..m]
     A[i..j, p, k..m]
(snip)
     A[0..$,3..$]
Yes, I would too. It is quite frustrating having the syntax in the language but not being allowed to utilize it... :) I work around this by instead using a custom slice syntax instead: A[range(i,j), range(k,m)] A[range(i,j), p, range(k,m)] A[range(0,end), range(3..end)] A[end-1, p % end]
Yeh, that's similar to what I'm doing too. But it's pretty ugly. So I guess that means you're using opIndex for everything and leaving opSlice alone.
Yes. Apart from the no-argument opSlice and opSliceAssign, that I overload with the same meaning as for the built in arrays. opSlice is not designed to support more than one dimension.
 Are you able to have ranges return arrays and specific indexes
 return scalar values that way?
Yes.
 That seems to me a big reason for having
 opSlice exist in the first place.  The .. in the brackets not only means
 you're slicing, it also means the function should return another array,
 versus returning an element.  That seems like a nice distinction to have
 to me.
Yes, definitely. My implementation mimics the behavior of the D native array/slice type (T[]), but extended for multiple dimensions and strides. So given Array!(2,int) A, A[all,5] and A[5,all] are both Array!(1,int) of the 6-th row and column of A. A[5,5] is an int. Some other neat features made possible by strided arrays are for instance A.diag, that returns the 1-dimensional diagonal of the array, so eg: A[] = 0; A.diag[] = 1; would construct the unit matrix.
Sounds great. Is your code available anywhere? --bb
Dec 26 2006
prev sibling next sibling parent Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Andrei Alexandrescu (See Website For Email)
(SeeWebsiteForEmail erdani.org)'s article
 Don Clugston wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Similarly, let's say that a group of revolutionaries convinces Walter
 (as I understand happened in case of using "length" and "$" inside
 slice expressions, which is a shame and an absolute disaster that must
 be undone at all costs) to implement "auto"
This off-hand remark worries me. I presume that you mean being able to reference the length of a string, from inside the slice? (rather than simply the notation). And the problem being that it requires a sliceable entity to know its length? Or is the problem more serious than that? It's worrying because any change would break an enormous amount of code.
It would indeed break an enormous amount of code, but "all costs" includes "enormous costs". :o) A reasonable migration path is to deprecate them soon and make them illegal over the course of one year. A small book could be written on just how bad language design is using "length" and "$" to capture slice size inside a slice expression. I managed to write two lengthy emails to Walter about them, and just barely got started. Long story short, "length" introduces a keyword through the back door, effectively making any use of "length" anywhere unrecommended and highly fragile. Using "$" is a waste of symbolic real estate to serve a narrow purpose; the semantics isn't naturally generalized to its logical conclusion; and the choice of symbol itself been vastly better as it has count connotation in natural language, and making it into an operator would have fixed the generalization issue). As things stand now, the rules governing the popping up of "length" and "$" constitute a sudden boo-boo on an otherwise carefully designed expression landscape.
I guess the question is, what is the best alternative. I agree about 'length', and I usually don't use "length" in this way, but I do things like x[$-2..$] all the time. Some proposals: 1. Symbols Going in the symbol direction, it might also make sense to *add* something like "^" for the start of a container. This would be useful with AAs and user defined types. We could use both: a[^+2..$-2]. This would only really be useful with containers that did not index from 0, i.e. non-integer or AA indices. char[char[]] words; words[^.."brink"]; // all words in dictionary before 'brink' words["brack"..$] // instead of symbols Which could translate to: words.opSlice(words.opBegin(), "brink") words.opSlice("brack", opEnd()) 2. I like this better: I call it "with without with" In order to maximize the dollar value :) of syntax symbol real estate, the meaning of $ could be expanded as follows: Something like X[$begin..$end] could be a shortcut for either X[0..X.length] for arrays, or X[X.opBegin()..X.opEnd()] for user types. I think the above solves the problem, doesn't it? The "$end" phrase is terse enough for most coders, unique enough to avoid namespace conflicts, avoids the problem of keywords ghosting in and out of existence in mid-expression, and We can stop right there... or go on to something for post-1.0: Other applications of $ could be: A. Syntax reduction for enumerated types and fields: struct Colors { enum { red, green, blue }; void set(int c); }; Colors c; c.set($red); This use of enumerated type is becoming more common, having "$" be a shortcut for <context>.X might make a lot of code more readable. The question then becomes, "Which contexts are searched for .X?" B. Reserved for language features. Leave this open for language designer use. All $xyz expressions are context dependent keywords. This allows much shorter words to be used, and allows language features to be named intelligently without worrying about crashing into user-defined names. For example, C could never introduce a new keyword called "begin" or "end", since it would break nearly every C program, but we can easily add a keyword called $begin which will not conflict with anything, since the $ saves us from conflicts. Most of the discussions for new features here have at least some arguments on how to add the new syntax for the feature, what other uses those symbols could be used for, etc. The $xyz route allows Walter to introduce lots of language concepts in the future without conflicts. It could even be used to prototype keywords that are experimental. They can even be removed or promoted to non-$ status later if desired. "#line" and "#file" quasi-keywords.
 These issues you're raising seem to be far too fundamental to be fixed
 in the next few days, casting grave doubts on whether a D1.0 release on
 Jan 1 is a good idea.
The lvalue/rvalue issue is fundamental. I'm not in the position to assess whether it's a maker or breaker of D 1.0. The "length"/"$" issue is not fundamental the same way that C's declaration syntax, Java's throw specifications, C++'s use of "<" and ">" for templates, and Mao Zedong's refusal to use a toothbrush are not fundamental. It will "just" go down in history as a huge embarrassment and a good resource for cheap shooters and naysayers. If I understand its genesis, it will also be a canonical example of why design by committee is bad. Andrei
I like the terseness of "$" but I'm willing to do away with it if it really is that bad. What I'm wondering, is how far do you think we need to roll back the syntax, before it's "The Right Thing" (tm) again? Do we really need to go all the way to myarray[0..myarray.length], or can some intermediate solution work? Kevin
Dec 21 2006
prev sibling parent Steve Horne <stephenwantshornenospam100 aol.com> writes:
On Wed, 20 Dec 2006 06:24:28 -0800, "Andrei Alexandrescu (See Website
For Email)" <SeeWebsiteForEmail erdani.org> wrote:

Long story short, "length" introduces a keyword 
through the back door, effectively making any use of "length" anywhere 
unrecommended and highly fragile.
I've always had a strong feeling that all-lowercase words should be reserved as potential future keywords anyway, barring a few special cases like 'i'. I suppose a lot of that comes down to style, though. If you have a convention where most identifiers start with a capital (excluding prefixes like 'l_', 'p_' or 'm_'), that only really leaves common short non-descriptive names like 'i' as a special case. It's one of the things that bugs me about the normal Java style - it allows all-lowercase identifiers (only using capitals as word separators), which look like potential keywords if you're not too familiar with Java. Besides, from natural language convention, capitals should appear at the start of a sentence - having them in the middle of an sentence-like identifier but not at the start just looks wrong to me. -- Remove 'wants' and 'nospam' from e-mail.
Dec 22 2006
prev sibling parent reply Benji Smith <dlanguage benjismith.net> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Let me illustrate further why ident is important and what solution we 
 should have for it. Consider C's response to ident:
 
 #define IDENT(e) (e)
 
 ...

 ...leading to the following implementation of ident:
 
 auto ident(auto x) {
   return x;
 }
I don't get it. Why is it necessary (or even desirable) for functions to return lvalues? I can see how it'd be an interesting trick, and I can appreciate the experimental curiosity about how the language (and the implementation) should cope with the explicit handling of lvalues. But I can't think of a real-world use case. Are there languages where this is currently possible? How do they implement it? And, much more importantly, what do people use it for? --benji
Dec 20 2006
next sibling parent reply Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Benji Smith wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Let me illustrate further why ident is important and what solution we 
 should have for it. Consider C's response to ident:

 #define IDENT(e) (e)
> ... >
 ...leading to the following implementation of ident:

 auto ident(auto x) {
   return x;
 }
I don't get it. Why is it necessary (or even desirable) for functions to return lvalues?
For functions, I essentially agree. There aren't many use cases for it, and for the few niche ones a pointer will often suffice. But for methods of classes on the other hand, it can be valuable at times.
 Are there languages where this is currently possible?
C++, by returning a referance. -- Chris Nicholson-Sauls
Dec 20 2006
parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Chris Nicholson-Sauls wrote:
 Benji Smith wrote:
 Are there languages where this is currently possible?
C++, by returning a referance.
Perl 5 too. Andrei
Dec 21 2006
prev sibling next sibling parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Benji Smith wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Let me illustrate further why ident is important and what solution we 
 should have for it. Consider C's response to ident:

 #define IDENT(e) (e)
> ... >
 ...leading to the following implementation of ident:

 auto ident(auto x) {
   return x;
 }
I don't get it. Why is it necessary (or even desirable) for functions to return lvalues?
Methods might want to return lvalues, but indeed the need is not overwhelming. (They could return pointers after all.) But the point is different. You want to have a grip on all types, and ident shows that you can't. For example, in current D you can't (barring a hack that I saw in a post around here) have a template that takes a function and creates one of the exact signature. That is a vastly useful and desirable thing to want; think e.g. of a function that memoizes any other function. Andrei
Dec 21 2006
prev sibling next sibling parent Charles D Hixson <charleshixsn earthlink.net> writes:
Benji Smith wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Let me illustrate further why ident is important and what solution we 
 should have for it. Consider C's response to ident:

 #define IDENT(e) (e)
> ... >
 ...leading to the following implementation of ident:

 auto ident(auto x) {
   return x;
 }
I don't get it. Why is it necessary (or even desirable) for functions to return lvalues? I can see how it'd be an interesting trick, and I can appreciate the experimental curiosity about how the language (and the implementation) should cope with the explicit handling of lvalues. But I can't think of a real-world use case. Are there languages where this is currently possible? How do they implement it? And, much more importantly, what do people use it for? --benji
FWIW, In PL/1 the substring function could be used as an lvalue. (Analogously, in D one can use array slices as lvalues.) I can't remember whether PL/1 allowed one to use this feature to delete characters, or only to alter them, but in Python one can use this feature to delete characters (or replace them with something that isn't a character?).
Dec 21 2006
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Benji Smith wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Let me illustrate further why ident is important and what solution we 
 should have for it. Consider C's response to ident:

 #define IDENT(e) (e)
> ... >
 ...leading to the following implementation of ident:

 auto ident(auto x) {
   return x;
 }
I don't get it. Why is it necessary (or even desirable) for functions to return lvalues? I can see how it'd be an interesting trick, and I can appreciate the experimental curiosity about how the language (and the implementation) should cope with the explicit handling of lvalues. But I can't think of a real-world use case. Are there languages where this is currently possible? How do they implement it? And, much more importantly, what do people use it for? --benji
There have been numerous cases here in the NG of people griping with this problem when using property-methods, or operator overloads such as opIndex, which both cannot return lvalues and thus have usage limitations. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Dec 24 2006
prev sibling next sibling parent reply Thomas Kuehne <thomas-dloop kuehne.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andrei Alexandrescu (See Website for Email) schrieb am 2006-12-20:
 Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website For Email)
 ...
 That remains to be seen, but I think the buck stops at functions. The
 problem of duplicating templates for inout (lvalues) and rvalues
 stays, but I have an idea about that, that I might tell about
 tomorrow.
<snip>
 We then discussed another solution that I won't bore you with, as it's 
 so wrong it hurts. My current thoughts navigate around two possible 
 solutions. One is to make the storage part of the template parameters:

 template ident(S T) {
    S T ident(S T e) { return e; }
 }

 When two adjacent symbols appear in a template parameter list, they 
 unambiguously denote a storage class followed by a type. So "S" can bind 
 to things like "in", "inout" etc., while "T" can bind to types.
Unambiguously? template Templ_1(int i) { } Is "int" now a type or a storage class?
 Another solution that works is to commit to the idea of associating the 
 storage class with the parameter (and divorce it from the type 
 entirely). In that case, the following syntax describes what happens:

 template ident(T) {
    storageof(e) T ident(storageof(e) T e) { return e; }
 }

 The storageof(symbol) meta-operator yields the storage of that symbol.
This seems to be much cleaner. How would storageof handle the following: mixin ident!(extern(C) inout float function(int)) foo; Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFFiUIQLK5blCcjpWoRAo38AJwNP1gCk52kIKuto9x76paeQq1LewCfUbrK zg3KJGIRH5wVoe/JeSR1aHE= =0HhD -----END PGP SIGNATURE-----
Dec 20 2006
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Thomas Kuehne wrote:
 We then discussed another solution that I won't bore you with, as it's 
 so wrong it hurts. My current thoughts navigate around two possible 
 solutions. One is to make the storage part of the template parameters:

 template ident(S T) {
    S T ident(S T e) { return e; }
 }

 When two adjacent symbols appear in a template parameter list, they 
 unambiguously denote a storage class followed by a type. So "S" can bind 
 to things like "in", "inout" etc., while "T" can bind to types.
Unambiguously? template Templ_1(int i) { } Is "int" now a type or a storage class?
It's a type because the symbol "int" is already bound as a keyword. There's no way (to the best of my knowledge) to specify two adjacent non-keyword symbols in a template parameter list.
 Another solution that works is to commit to the idea of associating the 
 storage class with the parameter (and divorce it from the type 
 entirely). In that case, the following syntax describes what happens:

 template ident(T) {
    storageof(e) T ident(storageof(e) T e) { return e; }
 }

 The storageof(symbol) meta-operator yields the storage of that symbol.
This seems to be much cleaner.
The more I think of it, the more I think it sucks :o).
 How would storageof handle the following:
 
 mixin ident!(extern(C) inout float function(int)) foo;
C does not have lvalue return types. Andrei
Dec 20 2006
parent reply Thomas Kuehne <thomas-dloop kuehne.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andrei Alexandrescu (See Website For Email) schrieb am 2006-12-20:
 Thomas Kuehne wrote:
 We then discussed another solution that I won't bore you with, as it's 
 so wrong it hurts. My current thoughts navigate around two possible 
 solutions. One is to make the storage part of the template parameters:

 template ident(S T) {
    S T ident(S T e) { return e; }
 }

 When two adjacent symbols appear in a template parameter list, they 
 unambiguously denote a storage class followed by a type. So "S" can bind 
 to things like "in", "inout" etc., while "T" can bind to types.
Unambiguously? template Templ_1(int i) { } Is "int" now a type or a storage class?
It's a type because the symbol "int" is already bound as a keyword. There's no way (to the best of my knowledge) to specify two adjacent non-keyword symbols in a template parameter list.
enum S{ FOO } template Templ(S T) { } mixin Templ!(S.FOO) bar; Do you consider S an keyword here? Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFFiWtKLK5blCcjpWoRAnRTAKCLuXgE7qH/OyabfPJ3aGaPRT0IaQCeLFy2 3PDyDfn8rcPDXpKq+m572HQ= =l6HF -----END PGP SIGNATURE-----
Dec 20 2006
parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Thomas Kuehne wrote:
 enum S{ FOO }
 template Templ(S T) { }
 mixin Templ!(S.FOO) bar;
 
 Do you consider S an keyword here?
You're right, it makes parsing dependent on the symbol table, breaking a nice property of D. Back to the drawing board. Andrei
Dec 21 2006
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Ohh, why didn't you start a new thread... I hate it when a thread gets 
so nested that one needs a widescreen monitor to view the topic list. :P

Andrei Alexandrescu (See Website for Email) wrote:
 Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website For Email)
 ...
 That remains to be seen, but I think the buck stops at functions. The
 problem of duplicating templates for inout (lvalues) and rvalues
 stays, but I have an idea about that, that I might tell about
 tomorrow.

 Andrei
Did you ever work out how to do this lvalue/rvalue idea?
I had to leave to Romania before having the time to post thoughts on the lvalue/rvalue discussion I've had with Walter on Saturday. We both agree that it's a serious problem with D's type system that needs fixing (and that he needs to do all the work :o)). I've continued to think of the issue, at least enough to figure that the solution we initially thought of is not sound. Walter is reluctant to offering the ability to overload on lvalue/rvalue, while it turns out that that can't be avoided. Let's return to my litmus test - the identity function ident(e), which can snuggle any expression e and leave its semantics unchanged. My thesis is that this function is an important test of a language's power. The starting point would be: template ident(T) { T ident(T e) { return e; } inout T ident(inout T e) { return e; } } The problem with this approach is that it doesn't scale. Right now "inout" is about the only interesting storage class of a function parameter, but "lazy" comes to mind (which I hope to get rid of soon via a much better solution) and later on we'll have "const", and each of these combinations will mean one more duplication of the ident body (and, by extension, of any function that wants to just pass the storage class outside). Walter had an idea along the line:
Hum, I wonder if the 'lazy' "storage class" (it's not really a storage class per se) is something that is worth, or even makes sense preserving. If you want to completely preserve an expression, then what are the properties/characteristics of that expression? The type is one property of course, then there is if the expression is an rvalue or lvalue, and also if it is const/final/readonly or something like that. But 'lazy' is not something that is a property/characteristic of an expression is it? It is a concept that exists only in parameter passing of function calls. What would be an ident function trying to "preserve" the lazyness of an expression?..
 template ident(T) {
   return T ident(return T e) { return e; }
 }
 
 which allows you to reuse the return keyword as a symbolic placeholder 
 for passing out the storage class. This solution is severely 
 shortsighted in that it fixes ident and only ident, whereas the purpose 
 of ident is to serve as a simplified case for functions with multiple 
 parameters. So this fell as well.
 
 We then discussed another solution that I won't bore you with, as it's 
 so wrong it hurts. My current thoughts navigate around two possible 
 solutions. One is to make the storage part of the template parameters:
 
 template ident(S T) {
   S T ident(S T e) { return e; }
 }
 
 When two adjacent symbols appear in a template parameter list, they 
 unambiguously denote a storage class followed by a type. So "S" can bind 
 to things like "in", "inout" etc., while "T" can bind to types. In the 
 example above, the compiler will deduce both S and T from the argument 
 type. It already does that, so that's no extra difficulty. The key point 
 that makes this scale is that you can bind S and T multiple times in a 
 variadic template. Another interesting detail is that it clarifies that 
 you can't solve the problem without somehow compiling two versions of 
 the ident function. So in the end overloading on "inout" is a must.
 
 Another solution that works is to commit to the idea of associating the 
 storage class with the parameter (and divorce it from the type 
 entirely). In that case, the following syntax describes what happens:
 
 template ident(T) {
   storageof(e) T ident(storageof(e) T e) { return e; }
 }
 
 The storageof(symbol) meta-operator yields the storage of that symbol. 
 The problem with this notation is that it uses a symbol without having 
 seen it. That's not too bad (it already happens due to the way symbols 
 at global scope are looked up) but in this case it does have a fishy 
 smell. Another thing that I don't like it that the code obscures what's 
 going on - namely that one ident will be generated for each storage 
 class, even though that's not reflected in the parameter type list.
 
 Finally, one related but slightly different topic is the necessity of 
 deduced return types for functions, e.g. by using "auto" to denote the 
 return type. Automatic deduction of return types is very useful in that 
 it allows compact template function definition - no more need for a 
 template that defines a homonym function. With deduced argument types, 
 ident can be written as:
 
 auto ident(S T)(S T e) {
   return e;
 }
 
 which is, I think, the Platonic ideal of ident as far as expressing it 
 in D goes.
 
 
 Andrei
Hum, interesting. I've seen this issue come up in another context as well, namely a paper about a proposal (Javari) for the addition of a reference immutability construct to Java, in the form of a 'const'-like keyword called 'readonly'. Since 'readonly' is very like (the future) 'inout', as they are both "type modifiers" of sorts, with similar syntax, they too had the issue of trying to avoid function definition duplication where only the absence or presence of 'readonly' changed in the prototype. Their solution was similar to the above. But instead of a "storage class" modifier variable like S, they used a keyword ('romaybe'), so that a templated function would be defined as this: romaybe Object getValue(romaybe thisojb) { return thisojb.value; } Which would generate two function instances, one where 'romaybe' is substituted by 'readonly' and other where it is substituted with nothing (which is mutability). The difference between the "storage class" modifier variable syntax is that only one "storage class" [*] can be parameterized, i.e., you can't have S1, S2, etc. [*] we really should not be calling this, "storage class". -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Dec 26 2006
prev sibling parent reply "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Lionello Lunesu wrote:
 Walter Bright wrote:
 It is possible to force the return type of opAssign. But I'd suggest 
 making it an S*, with the rewrite to:

     *(a.opAssign(b))
class C { C* opAssign(...) {...} } struct S { S* opAssign(...) {...} } Seriously?? Ugh. Why not just "C opAssign" and "S opAssign"? In the opCall discussion you said yourself that the return value will be optimized. Am I missing something?
There is no *extra* copying, in the sense that exactly one copy is done. But most of the time the copy obtained is not used, in which case that one copy is spurious. Andrei
Dec 14 2006
parent reply Lionello Lunesu <lio lunesu.remove.com> writes:
Andrei Alexandrescu (See Website for Email) wrote:
 Lionello Lunesu wrote:
 Walter Bright wrote:
 It is possible to force the return type of opAssign. But I'd suggest 
 making it an S*, with the rewrite to:

     *(a.opAssign(b))
class C { C* opAssign(...) {...} } struct S { S* opAssign(...) {...} } Seriously?? Ugh. Why not just "C opAssign" and "S opAssign"? In the opCall discussion you said yourself that the return value will be optimized. Am I missing something?
There is no *extra* copying, in the sense that exactly one copy is done. But most of the time the copy obtained is not used, in which case that one copy is spurious.
I'd think that the optimizer would not only eliminate the extra copy, but the entire construction of the return value .. No? L.
Dec 15 2006
parent Sean Kelly <sean f4.ca> writes:
Lionello Lunesu wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 Lionello Lunesu wrote:
 Walter Bright wrote:
 It is possible to force the return type of opAssign. But I'd suggest 
 making it an S*, with the rewrite to:

     *(a.opAssign(b))
class C { C* opAssign(...) {...} } struct S { S* opAssign(...) {...} } Seriously?? Ugh. Why not just "C opAssign" and "S opAssign"? In the opCall discussion you said yourself that the return value will be optimized. Am I missing something?
There is no *extra* copying, in the sense that exactly one copy is done. But most of the time the copy obtained is not used, in which case that one copy is spurious.
I'd think that the optimizer would not only eliminate the extra copy, but the entire construction of the return value .. No?
If the routine is inlined perhaps, but otherwise not. Sean
Dec 15 2006
prev sibling parent "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Walter Bright wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Require opAssign() to always return void and have the compiler return 
 a reference to the left-hand side. That is, transform:

 a = b;

 into:

 (a.opAssign(b), a);

 with the mention that a only gets evaluated once.

 This way assignment _always_ returns its left-hand side and user code 
 cannot subvert that behavior. Also the code will be efficient because 
 no more spurious copies are being made.
It is possible to force the return type of opAssign. But I'd suggest making it an S*, with the rewrite to: *(a.opAssign(b))
Makes sense, but then I keep on racking my brain to ever think of any C++ code that ever return something else than *this. Never saw any, never wrote any - ever. So I just conclude that customizing =, +=, -= etc. is a good thing, but customizing their return type and value is not. Adding the silly "return this;" litany at the end of every single operator is not something for a language that wants to do things the right way. The speed will be better, too. It's kind of annoying to know that I can't write: a = b; and simply have the compiler do what it takes and no more. Every of those spurious copies is just some more discomfort I have to put in. Andrei
Dec 14 2006
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Andrei Alexandrescu (See Website For Email)
(SeeWebsiteForEmail erdani.org)'s article
 Walter Bright wrote:
 Andrei Alexandrescu (See Website for Email) wrote:

 That makes me wonder - if a = b is used without picking up its result
 (the usual case), and if opAssign() returns an S, will the compiler
 optimize away the extra copy at zero cost?
No. The caller and callee don't know about each other.
 What I think happens is that the function will take a pointer to the
 destination which is zero, so a comparison will be made. But perhaps
 the optimizer will take care of eliminating that?
You could have a null pointer passed to the return result, but that would entail an extra check on the part of the callee. This is why a lot of C++ code tends to return references instead of values.
I guess compiling internally two versions, one that returns S and one that returns void, might be worth looking into. Because right now user code for opAssign() can't be politically correct and efficient at the same time. Here's a better alternative: Require opAssign() to always return void and have the compiler return a reference to the left-hand side. That is, transform: a = b; into: (a.opAssign(b), a); with the mention that a only gets evaluated once. This way assignment _always_ returns its left-hand side and user code cannot subvert that behavior. Also the code will be efficient because no more spurious copies are being made. Andrei
I wonder, are there cases where assignment should not return 'this'? What I'm thinking of is something like: class StringExpression { ... holds equations between strings }; class String { // wrapper around char[] StringExpression opCat(String x); StringExpression opCat(StringExpression x); }; This 'expression-class' stuff is done more commonly with matrices, where the programmer is trying to to combine multiplies and adds via some optimization rules. Since C++ at least is flexible (syntax wise at least) on operator inputs and outputs, I have to wonder if there are really consequences for not following those expectations here. Imagine a string expression like this: a = b ~ (c = d ~ e); Now if I can do "StringExpression String::opAssign(...)", this could maybe be rewritten (code not shown...) to result in something like this: a = b ~ d ~ e; c = a[b.length..a.length]; Not that this is worth the effort or that the language should do this with strings, but as a programmer, can I reasonably do this kind of trick with operator return values or am I just digging a hole for myself? (I realize that even if useful it may not be worthwhile.) Kevin
Dec 14 2006
parent Sean Kelly <sean f4.ca> writes:
Kevin Bealer wrote:
 == Quote from Andrei Alexandrescu (See Website For Email)
 (SeeWebsiteForEmail erdani.org)'s article
 Here's a better alternative:
 Require opAssign() to always return void and have the compiler return a
 reference to the left-hand side. That is, transform:
 a = b;
 into:
 (a.opAssign(b), a);
 with the mention that a only gets evaluated once.
 This way assignment _always_ returns its left-hand side and user code
 cannot subvert that behavior. Also the code will be efficient because no
 more spurious copies are being made.
I wonder, are there cases where assignment should not return 'this'?
I've been thinking the same thing, and so far haven't been able to come up with an example where "correct" use of an assignment operator should not return 'this'. I was considering suggestion the same thing as Andrei above, but was hoping someone would post a counterexample in the interim :-) About the only weird thing with returning void and allowing this: a = b = c; Is that it isn't equivalent to this: a.opAssign( b.opAssign( c ) ); Which may be a bit confusing. But it does seem quite natural that the result of an assignment should always be the LHS of the assignment. Sean
Dec 14 2006
prev sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Walter Bright wrote:
 Stewart Gordon wrote:
<snip>
 The programmer would have a choice - opAssign returning void to modify 
 in-place the object referenced by the lvalue, or returning a new 
 object that will be assigned to the lvalue.  What is this precluding?
opAssign has 3 externally visible characteristics: 1) the parameter 2) the 'this' pointer 3) the return value Your proposal mixes up 2 and 3. opAssign works like: a = b becomes: a.opAssign(b) The return value is not assigned to a, it is the value of the expression (a = b). Mixing up the return value and the assignment to a will cause problems, as the two are different things, and should be independent.
C'mon, what's your use case for being allowed to return something other than the new value of a from the expression (a = b)? Stewart.
Dec 14 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Stewart Gordon wrote:
 C'mon, what's your use case for being allowed to return something other 
 than the new value of a from the expression (a = b)?
I don't have one. But I prefer to impose as few restrictions as possible, as people keep finding unanticipated cool new things to do. So I'd argue that there should be compelling case put forward to impose such a restriction, rather than wondering what one can do with the flexibility.
Dec 14 2006
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Walter Bright wrote:
 Stewart Gordon wrote:
 C'mon, what's your use case for being allowed to return something 
 other than the new value of a from the expression (a = b)?
I don't have one. But I prefer to impose as few restrictions as possible, as people keep finding unanticipated cool new things to do. So I'd argue that there should be compelling case put forward to impose such a restriction, rather than wondering what one can do with the flexibility.
For one, it might be useful to return some sort of proxy for 'a'. --bb
Dec 14 2006
prev sibling next sibling parent "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Walter Bright wrote:
 Stewart Gordon wrote:
 C'mon, what's your use case for being allowed to return something 
 other than the new value of a from the expression (a = b)?
I don't have one. But I prefer to impose as few restrictions as possible, as people keep finding unanticipated cool new things to do. So I'd argue that there should be compelling case put forward to impose such a restriction, rather than wondering what one can do with the flexibility.
Just did so in my previous post. Andrei
Dec 14 2006
prev sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Walter Bright wrote:
 Stewart Gordon wrote:
 C'mon, what's your use case for being allowed to return something 
 other than the new value of a from the expression (a = b)?
I don't have one. But I prefer to impose as few restrictions as possible, as people keep finding unanticipated cool new things to do. So I'd argue that there should be compelling case put forward to impose such a restriction, rather than wondering what one can do with the flexibility.
We already have what strikes the rest of us as a compelling case: what we've just told you that returning from opAssign _should_ be able to do. Stewart.
Dec 14 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Stewart Gordon wrote:
 We already have what strikes the rest of us as a compelling case: what 
 we've just told you that returning from opAssign _should_ be able to do.
That case was trying to make opAssign be a constructor. Since there are other ways to construct it, I don't see why it's a compelling case. What is bought with this that cannot be otherwise done?
Dec 14 2006
parent Stewart Gordon <smjg_1998 yahoo.com> writes:
Walter Bright wrote:
 Stewart Gordon wrote:
 We already have what strikes the rest of us as a compelling case: what 
 we've just told you that returning from opAssign _should_ be able to do.
That case was trying to make opAssign be a constructor. Since there are other ways to construct it, I don't see why it's a compelling case. What is bought with this that cannot be otherwise done?
The cases you seemingly pander to for having opAssign in the first place are trying to make opAssign be a property setter. Since there are other ways to set properties, I don't see why this is a compelling case. What is bought with this that cannot be otherwise done? Stewart.
Dec 15 2006
prev sibling next sibling parent reply "Chris Miller" <chris dprogramming.com> writes:
char* p =3D new char[32];

Error: cannot implicitly convert expression (new char[](32)) of type  =

char[] to char*

Should this be a special case? Currently it needs  (new char[32]).ptr
Dec 09 2006
next sibling parent reply Alexander Panek <a.panek brainsware.org> writes:
char *newCharz( uint size ) {
	return (new char [size]).ptr;
}

char *p = newCharz(32);

:P

I like the explicity.

Chris Miller wrote:
 
 char* p = new char[32];
 
 Error: cannot implicitly convert expression (new char[](32)) of type 
 char[] to char*
 
 Should this be a special case? Currently it needs  (new char[32]).ptr
Dec 09 2006
parent reply "Chris Miller" <chris dprogramming.com> writes:
On Sat, 09 Dec 2006 11:33:17 -0500, Alexander Panek  =

<a.panek brainsware.org> wrote:

 char *newCharz( uint size ) {
 	return (new char [size]).ptr;
 }

 char *p =3D newCharz(32);

 :P

 I like the explicity.
Well, I'm not sure what it should be, but you already made a mistake: it= = should be size_t instead of uint ;) Plus, I never said it was a zero-terminated string.
 Chris Miller wrote:
  char* p =3D new char[32];
  Error: cannot implicitly convert expression (new char[](32)) of type=
=
 char[] to char*
  Should this be a special case? Currently it needs  (new char[32]).pt=
r
Dec 09 2006
parent reply Alexander Panek <a.panek brainsware.org> writes:
Chris Miller wrote:
 On Sat, 09 Dec 2006 11:33:17 -0500, Alexander Panek 
 <a.panek brainsware.org> wrote:
 
 char *newCharz( uint size ) {
     return (new char [size]).ptr;
 }

 char *p = newCharz(32);

 :P

 I like the explicity.
Well, I'm not sure what it should be, but you already made a mistake: it should be size_t instead of uint ;) Plus, I never said it was a zero-terminated string.
Oi. Sorry: char * toChars( size_t size ) { return (new char[size]).ptr; } char * toCharz( size_t size ) { return (new char[size + 1]).ptr; } Better? :P
 
 Chris Miller wrote:
  char* p = new char[32];
  Error: cannot implicitly convert expression (new char[](32)) of type 
 char[] to char*
  Should this be a special case? Currently it needs  (new char[32]).ptr
Dec 09 2006
parent reply Alexander Panek <a.panek brainsware.org> writes:
Those are newCharz and newChars, of course.

Apart from that, this might be interesting, too:

// alias thingie array;

template Ptr (T) {
	// Create a simple thingie
	T *newPtrs ( size_t size ) {
		return (new T[size]).ptr;
	}

	// Create null-terminated thingie
	T *newPtrz ( size_t size ) {
		T *p = (new T[size + 1]).ptr
		p[$ - 1] = '\0';

		return p;
	}
}

alias Ptr!(char).newPtrs newChars;
alias Ptr!(char).newPtrz newCharz;

Not tested, but this might be useful for someone.

Alexander Panek wrote:
 Chris Miller wrote:
 On Sat, 09 Dec 2006 11:33:17 -0500, Alexander Panek 
 <a.panek brainsware.org> wrote:

 char *newCharz( uint size ) {
     return (new char [size]).ptr;
 }

 char *p = newCharz(32);

 :P

 I like the explicity.
Well, I'm not sure what it should be, but you already made a mistake: it should be size_t instead of uint ;) Plus, I never said it was a zero-terminated string.
Oi. Sorry: char * toChars( size_t size ) { return (new char[size]).ptr; } char * toCharz( size_t size ) { return (new char[size + 1]).ptr; } Better? :P
 Chris Miller wrote:
  char* p = new char[32];
  Error: cannot implicitly convert expression (new char[](32)) of 
 type char[] to char*
  Should this be a special case? Currently it needs  (new char[32]).ptr
Dec 09 2006
parent "Chris Miller" <chris dprogramming.com> writes:
On Sat, 09 Dec 2006 11:53:07 -0500, Alexander Panek  =

<a.panek brainsware.org> wrote:

 Those are newCharz and newChars, of course.

 Apart from that, this might be interesting, too:

 // alias thingie array;

 template Ptr (T) {
 	// Create a simple thingie
 	T *newPtrs ( size_t size ) {
 		return (new T[size]).ptr;
 	}

 	// Create null-terminated thingie
 	T *newPtrz ( size_t size ) {
 		T *p =3D (new T[size + 1]).ptr
 		p[$ - 1] =3D '\0';

 		return p;
 	}
 }

 alias Ptr!(char).newPtrs newChars;
 alias Ptr!(char).newPtrz newCharz;

 Not tested, but this might be useful for someone.
Not bad, perhaps needs better names though. newChars still sounds like n= ew = char[] (array). Maybe allocptr/ptrAlloc/ptralloc/palloc to have a simila= r = name as malloc since they have similarities (working with pointers). = *shrug*
Dec 09 2006
prev sibling parent Pragma <ericanderton yahoo.removeme.com> writes:
Chris Miller wrote:
 
 char* p = new char[32];
 
 Error: cannot implicitly convert expression (new char[](32)) of type 
 char[] to char*
 
 Should this be a special case? Currently it needs  (new char[32]).ptr
Well, the problem is that since this change, char[] is not the same thing as char*; IMO, this is a good thing. You really should have to jump through an extra hoop to get there, because they're not the same. It's no different than a cast, if it is a little harder to type. -- - EricAnderton at yahoo
Dec 11 2006
prev sibling next sibling parent reply Burton Radons <burton-radons smocky.com> writes:
- Casting a value v to a struct S is now rewritten as S(v).

I'm 100% against this. This is what C++ does, and it conflates 
construction and casting; with some VERY simple examples (such as the 
first one we think of when we think of additional types, bignums), 
there's an ambiguous conflict because one of the constructors should 
have a count of how many digits you want - and one of the casters takes 
an integer for a value to initialise to. My solution at the time was to 
add a dummy argument to the constructor so that the compiler didn't try 
to match them, which is absurd.

I don't know why C++ was designed like this when its error was so 
blatant, but D doesn't need to replicate its mistake. "S.opCastFrom (v)" 
please, except that it shouldn't be static either.
Dec 09 2006
parent reply Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Burton Radons wrote:
 - Casting a value v to a struct S is now rewritten as S(v).
 
 I'm 100% against this. This is what C++ does, and it conflates 
 construction and casting; with some VERY simple examples (such as the 
 first one we think of when we think of additional types, bignums), 
 there's an ambiguous conflict because one of the constructors should 
 have a count of how many digits you want - and one of the casters takes 
 an integer for a value to initialise to. My solution at the time was to 
 add a dummy argument to the constructor so that the compiler didn't try 
 to match them, which is absurd.
 
 I don't know why C++ was designed like this when its error was so 
 blatant, but D doesn't need to replicate its mistake. "S.opCastFrom (v)" 
 please, except that it shouldn't be static either.
Essentially agreed. I'm not entirely fond of the "silent" new opAssign either, because of the visual ambiguity it can lead to. (Its worth noting that, even though '='->opAssign is listed in the spec, the bottom of the same page still lists '=' among the operators which will not be given overloads. Hm.) -- Chris Nicholson-Sauls
Dec 09 2006
parent reply Sean Kelly <sean f4.ca> writes:
Chris Nicholson-Sauls wrote:
 Burton Radons wrote:
 - Casting a value v to a struct S is now rewritten as S(v).

 I'm 100% against this. This is what C++ does, and it conflates 
 construction and casting; with some VERY simple examples (such as the 
 first one we think of when we think of additional types, bignums), 
 there's an ambiguous conflict because one of the constructors should 
 have a count of how many digits you want - and one of the casters 
 takes an integer for a value to initialise to. My solution at the time 
 was to add a dummy argument to the constructor so that the compiler 
 didn't try to match them, which is absurd.

 I don't know why C++ was designed like this when its error was so 
 blatant, but D doesn't need to replicate its mistake. "S.opCastFrom 
 (v)" please, except that it shouldn't be static either.
Essentially agreed. I'm not entirely fond of the "silent" new opAssign either, because of the visual ambiguity it can lead to. (Its worth noting that, even though '='->opAssign is listed in the spec, the bottom of the same page still lists '=' among the operators which will not be given overloads. Hm.)
Same. And frankly, I'm having trouble coming up with a practical use for the new opAssign. For one thing, it isn't commutative: auto c = new MyClass; int i = 5; c = i; // will work i = c; // won't work And now structs have an opAssign but still have binary copy semantics and no ctor/dtor support. The result is totally confusing. What was the use case for this feature? Sean
Dec 09 2006
parent Leopold Walkling <leopold_walkling web.de> writes:
Sean Kelly schrieb:
 Chris Nicholson-Sauls wrote:
 Burton Radons wrote:
 - Casting a value v to a struct S is now rewritten as S(v).

 I'm 100% against this. This is what C++ does, and it conflates 
 construction and casting; with some VERY simple examples (such as the 
 first one we think of when we think of additional types, bignums), 
 there's an ambiguous conflict because one of the constructors should 
 have a count of how many digits you want - and one of the casters 
 takes an integer for a value to initialise to. My solution at the 
 time was to add a dummy argument to the constructor so that the 
 compiler didn't try to match them, which is absurd.

 I don't know why C++ was designed like this when its error was so 
 blatant, but D doesn't need to replicate its mistake. "S.opCastFrom 
 (v)" please, except that it shouldn't be static either.
Essentially agreed. I'm not entirely fond of the "silent" new opAssign either, because of the visual ambiguity it can lead to. (Its worth noting that, even though '='->opAssign is listed in the spec, the bottom of the same page still lists '=' among the operators which will not be given overloads. Hm.)
Same. And frankly, I'm having trouble coming up with a practical use for the new opAssign. For one thing, it isn't commutative: auto c = new MyClass; int i = 5; c = i; // will work i = c; // won't work And now structs have an opAssign but still have binary copy semantics and no ctor/dtor support. The result is totally confusing. What was the use case for this feature? Sean
Why are opAssign overloads possible for structs? They lead to a bad style like the example above: It's possible to assign a primitive type to an aggregate type, without showing what is done in the code.
Dec 09 2006
prev sibling next sibling parent "John Reimer" <terminal.node gmail.com> writes:
On Sat, 09 Dec 2006 02:20:00 -0800, Walter Bright  =

<newshound digitalmars.com> wrote:

 More ABI changes, and implicit [] =3D> * no longer allowed.

 http://www.digitalmars.com/d/changelog.html

 http://ftp.digitalmars.com/dmd.175.zip
um... but this is the link for dmd.175.zip again, not 177. -JJR
Dec 09 2006
prev sibling next sibling parent pmoore <some_email_addr example.com> writes:
Thanks for this. But std.demangle seems to be broken now.
Dec 09 2006
prev sibling next sibling parent reply "David L. Davis" <SpottedTiger yahoo.com> writes:
Walter in dmd v0.176 you improved name mangling, but I've found one very 
confusing change...is it your intension to reuse the "b" which used to mean 
"bit," to now mean a "bool" instead of "x?"

For example, one function "real vdb(real, real, real, real, real, real, bool 
= false)" used in my financial D dll,
before dmd v0.148 was released when the "bool" data type was added 
(replacing the "bit" data type) the function's mangled name was 
"D13financial_dll3vdbFeeeeeebZe". And with dmd v0.148 and later, the "bit" 
data type become an alias to bool and all the "b" (for "bit") were replaced 
with "x" (for "bool"), thus the funtion's mangled name changed to become 
"D13financial_dll3vdbFeeeeeexZe". But now it looks like "x" has been 
replaced by "b," is this a mistake on your part, or have you decided to use 
a "b" as meaning a "bool" (discarding "x")?

So currently the "vdb()" funtion under dmd v0.177 has returned to the old 
mangled name "D13financial_dll3vdbFeeeeeebZe," as if a "bool" has become a 
"bit" again.

Thanks again for all the great work you've been putting into D year after 
year!

David L.
-------------------------------------------------------------------
"Dare to reach for the Stars...Dare to Dream, Build, and Achieve!"
-------------------------------------------------------------------

MKoD: http://spottedtiger.tripod.com/D_Language/D_Main_XP.html
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:ele2k9$2hr5$1 digitaldaemon.com...
 More ABI changes, and implicit [] => * no longer allowed.

 http://www.digitalmars.com/d/changelog.html

 http://ftp.digitalmars.com/dmd.175.zip 
Dec 09 2006
next sibling parent Walter Bright <newshound digitalmars.com> writes:
David L. Davis wrote:
 Walter in dmd v0.176 you improved name mangling, but I've found one very 
 confusing change...is it your intension to reuse the "b" which used to mean 
 "bit," to now mean a "bool" instead of "x?"
That's what people asked for.
 For example, one function "real vdb(real, real, real, real, real, real, bool 
 = false)" used in my financial D dll,
 before dmd v0.148 was released when the "bool" data type was added 
 (replacing the "bit" data type) the function's mangled name was 
 "D13financial_dll3vdbFeeeeeebZe". And with dmd v0.148 and later, the "bit" 
 data type become an alias to bool and all the "b" (for "bit") were replaced 
 with "x" (for "bool"), thus the funtion's mangled name changed to become 
 "D13financial_dll3vdbFeeeeeexZe". But now it looks like "x" has been 
 replaced by "b," is this a mistake on your part, or have you decided to use 
 a "b" as meaning a "bool" (discarding "x")?
That's correct.
 So currently the "vdb()" funtion under dmd v0.177 has returned to the old 
 mangled name "D13financial_dll3vdbFeeeeeebZe," as if a "bool" has become a 
 "bit" again.
Yes. There's no going back to bit.
 Thanks again for all the great work you've been putting into D year after 
 year!
You're most welcome.
Dec 09 2006
prev sibling parent reply Don Clugston <dac nospam.com.au> writes:
David L. Davis wrote:
 Walter in dmd v0.176 you improved name mangling, but I've found one very 
 confusing change...is it your intension to reuse the "b" which used to mean 
 "bit," to now mean a "bool" instead of "x?"
 For example, one function "real vdb(real, real, real, real, real, real, bool 
 = false)" used in my financial D dll,
 before dmd v0.148 was released when the "bool" data type was added 
 (replacing the "bit" data type) the function's mangled name was 
 "D13financial_dll3vdbFeeeeeebZe". And with dmd v0.148 and later, the "bit" 
 data type become an alias to bool and all the "b" (for "bit") were replaced 
 with "x" (for "bool"), thus the funtion's mangled name changed to become 
 "D13financial_dll3vdbFeeeeeexZe". But now it looks like "x" has been 
 replaced by "b," is this a mistake on your part, or have you decided to use 
 a "b" as meaning a "bool" (discarding "x")?
Between 0.148 and 0.175, function pointers used 'b' for 'bool', but the functions themselves used 'x'. It was a bit inconsistent.
Dec 10 2006
parent "David L. Davis" <SpottedTiger yahoo.com> writes:
Don thanks for clarifying the reason, I didn't know about the function 
pointers still using 'b' after bool replaced bit. It all now makes a lot 
more sense as to the why change happened the way it did.

David L.
-------------------------------------------------------------------
"Dare to reach for the Stars...Dare to Dream, Build, and Achieve!"
-------------------------------------------------------------------

MKoD: http://spottedtiger.tripod.com/D_Language/D_Main_XP.html

"Don Clugston" <dac nospam.com.au> wrote in message 
news:elho1o$t1d$1 digitaldaemon.com...
 David L. Davis wrote:
 Walter in dmd v0.176 you improved name mangling, but I've found one very 
 confusing change...is it your intension to reuse the "b" which used to 
 mean "bit," to now mean a "bool" instead of "x?"
 For example, one function "real vdb(real, real, real, real, real, real, 
 bool = false)" used in my financial D dll,
 before dmd v0.148 was released when the "bool" data type was added 
 (replacing the "bit" data type) the function's mangled name was 
 "D13financial_dll3vdbFeeeeeebZe". And with dmd v0.148 and later, the 
 "bit" data type become an alias to bool and all the "b" (for "bit") were 
 replaced with "x" (for "bool"), thus the funtion's mangled name changed 
 to become "D13financial_dll3vdbFeeeeeexZe". But now it looks like "x" has 
 been replaced by "b," is this a mistake on your part, or have you decided 
 to use a "b" as meaning a "bool" (discarding "x")?
Between 0.148 and 0.175, function pointers used 'b' for 'bool', but the functions themselves used 'x'. It was a bit inconsistent.
Dec 12 2006
prev sibling next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:ele2k9$2hr5$1 digitaldaemon.com...
 More ABI changes, and implicit [] => * no longer allowed.

 http://www.digitalmars.com/d/changelog.html

 http://ftp.digitalmars.com/dmd.175.zip
No offense, but would it honestly kill you to allow ctors in structs? We've been using static opCall as a _workaround_ for the lack of struct ctors, and making it part of the language doesn't really seem to be addressing the problem. That, and it doesn't make any sense that classes use "this()" and structs use "static S opCall()". We're starting to get into "overloading indexing in C++" territory. That, and which would be more efficient? The static opCall needs to return an instance of the struct on the stack, which could be very inefficient for large structs. With a true ctor, the struct is passed by reference to the ctor and has its fields set directly. And I also can't figure out how to make the implicit opCall work with more than one parameter. :P I'm also a little wary about opAssign. Not that I don't find it useful, just that you've been vehemently opposed to overloading it in D for how many years, and three weeks before 1.0 you reverse your opinion. What does that mean? But complaints aside, thanks for removing the []=>* implicit cast and the bugfixes. Man, this last month has been an exciting time for D :)
Dec 09 2006
next sibling parent Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Jarrett Billingsley wrote:
 "Walter Bright" <newshound digitalmars.com> wrote in message 
 news:ele2k9$2hr5$1 digitaldaemon.com...
 
More ABI changes, and implicit [] => * no longer allowed.

http://www.digitalmars.com/d/changelog.html

http://ftp.digitalmars.com/dmd.175.zip
No offense, but would it honestly kill you to allow ctors in structs? We've been using static opCall as a _workaround_ for the lack of struct ctors, and making it part of the language doesn't really seem to be addressing the problem. That, and it doesn't make any sense that classes use "this()" and structs use "static S opCall()". We're starting to get into "overloading indexing in C++" territory. That, and which would be more efficient? The static opCall needs to return an instance of the struct on the stack, which could be very inefficient for large structs. With a true ctor, the struct is passed by reference to the ctor and has its fields set directly. And I also can't figure out how to make the implicit opCall work with more than one parameter. :P I'm also a little wary about opAssign. Not that I don't find it useful, just that you've been vehemently opposed to overloading it in D for how many years, and three weeks before 1.0 you reverse your opinion. What does that mean?
The thing with opAssign, I realised after reading the spec, is that it explicitly doesn't allow copy assignment. This reduces it from the level of a powerful feature to a mere toy, or at most a form of implicit casting. Assigning one struct to another still guarantees a bitwise copy, and reference assignment for class instances cannot be stepped on. This removes essentially all of the danger that opAssign might have introduced.
 But complaints aside, thanks for removing the []=>* implicit cast and the 
 bugfixes.  Man, this last month has been an exciting time for D :) 
 
 
-- Kirk McDonald Pyd: Wrapping Python with D http://pyd.dsource.org
Dec 09 2006
prev sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Jarrett Billingsley wrote:
 No offense, but would it honestly kill you to allow ctors in structs?  We've 
 been using static opCall as a _workaround_ for the lack of struct ctors, and 
 making it part of the language doesn't really seem to be addressing the 
 problem.  That, and it doesn't make any sense that classes use "this()" and 
 structs use "static S opCall()".  We're starting to get into "overloading 
 indexing in C++" territory.
It turns out that static opCall() and this() are equivalent. C++ has some obscure rules to try to disambiguate them. I wished to avoid that problem, and since static opCall() is routinely in use, picked them.
 That, and which would be more efficient?  The static opCall needs to return 
 an instance of the struct on the stack, which could be very inefficient for 
 large structs.  With a true ctor, the struct is passed by reference to the 
 ctor and has its fields set directly.
The optimizer removes the redundant copy, and builds the result directly into the target.
 And I also can't figure out how to make the implicit opCall work with more 
 than one parameter.  :P
S(1,2,3)
 I'm also a little wary about opAssign.  Not that I don't find it useful, 
 just that you've been vehemently opposed to overloading it in D for how many 
 years, and three weeks before 1.0 you reverse your opinion.  What does that 
 mean?
The problem I have, and still have, is the identity assignment. This is specifically disallowed with opAssign. I can go into the reasons why if you like. Having opAssign for foreign types turns out to be needed for things like, say I want to build a ranged integer. This is an integer that can only have values between m and n. Without opAssign, I'd have to use a property: RangedInt!(m,n) r; r.value = 6; It's just not right.
 But complaints aside, thanks for removing the []=>* implicit cast and the 
 bugfixes.  Man, this last month has been an exciting time for D :) 
For me as well!
Dec 09 2006
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:elf9mn$snp$1 digitaldaemon.com...

 It turns out that static opCall() and this() are equivalent. C++ has some 
 obscure rules to try to disambiguate them. I wished to avoid that problem, 
 and since static opCall() is routinely in use, picked them.
For classes they're not. Why should structs be any different? And even if static opCalls are routinely in use, are they the best solution? And are they _really_ equivalent? Where's the 'this' pointer in a static opCall()?
 The optimizer removes the redundant copy, and builds the result directly 
 into the target.
Wouldn't it be easier to just have ctors, so implementations don't have to worry about writing an optimizer just to support this behavior?
 And I also can't figure out how to make the implicit opCall work with 
 more than one parameter.  :P
S(1,2,3)
Oh. I was thinking more along the lines of being able to do "S s(1, 2, 3);".
 The problem I have, and still have, is the identity assignment. This is 
 specifically disallowed with opAssign. I can go into the reasons why if 
 you like.

 Having opAssign for foreign types turns out to be needed for things like, 
 say I want to build a ranged integer. This is an integer that can only 
 have values between m and n. Without opAssign, I'd have to use a property:
 RangedInt!(m,n) r;
 r.value = 6;
 It's just not right.
That makes sense. It gives the utility of opAssign in most cases without the scary stuff. Though as was mentioned elsewhere, it's not commutative, so it's still not possible to do RangedInt!(m, n) r; r = 5; int x = r; // error :/
 But complaints aside, thanks for removing the []=>* implicit cast and the 
 bugfixes.  Man, this last month has been an exciting time for D :)
For me as well!
Dec 09 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Jarrett Billingsley wrote:
 It turns out that static opCall() and this() are equivalent. C++ has some 
 obscure rules to try to disambiguate them. I wished to avoid that problem, 
 and since static opCall() is routinely in use, picked them.
For classes they're not. Why should structs be any different? And even if static opCalls are routinely in use, are they the best solution?
I've found they work well.
 And are they _really_ equivalent?
Not perfectly, but the differences are small.
 Where's the 'this' pointer in a static 
 opCall()?
It's in your return value.
 The optimizer removes the redundant copy, and builds the result directly 
 into the target.
Wouldn't it be easier to just have ctors, so implementations don't have to worry about writing an optimizer just to support this behavior?
You have to have the optimization anyway. My C++ compiler has had it since 1991 or so. It's oooold technology.
 And I also can't figure out how to make the implicit opCall work with 
 more than one parameter.  :P
S(1,2,3)
Oh. I was thinking more along the lines of being able to do "S s(1, 2, 3);".
I prefer: auto s = S(1,2,3); to C++ style. static opCall fits right in.
 Though as was mentioned elsewhere, it's not commutative, so it's still not 
 possible to do
 
 RangedInt!(m, n) r;
 r = 5;
 int x = r; // error
That's implicit casting from a struct. It'll probably wind up getting supported in one form or another.
Dec 09 2006
next sibling parent reply Burton Radons <burton-radons smocky.com> writes:
Walter Bright wrote:
 Jarrett Billingsley wrote:
 It turns out that static opCall() and this() are equivalent. C++ has 
 some obscure rules to try to disambiguate them. I wished to avoid 
 that problem, and since static opCall() is routinely in use, picked 
 them.
For classes they're not. Why should structs be any different? And even if static opCalls are routinely in use, are they the best solution?
I've found they work well.
Do you have a single instance of a static opCall being used for construction where you don't just create a variable on the stack, build it, and return it? Because that's all I've ever done. Hundreds of times. Nothing else, and it just ends up being useless fluff making code harder to read than it should be. The opposite end of this would be so easy - if classes didn't have constructors. They don't actually need them after all - you just need to have the ability to create a blank instance. But can you see how much of a pointless pain in the ass that would be? That's exactly how it is in structs right now.
 And I also can't figure out how to make the implicit opCall work 
 with more than one parameter.  :P
S(1,2,3)
Oh. I was thinking more along the lines of being able to do "S s(1, 2, 3);".
I prefer: auto s = S(1,2,3); to C++ style. static opCall fits right in.
Yeah, I've never liked C++'s handling of this, it looks way too much like a function call.
Dec 09 2006
parent Walter Bright <newshound digitalmars.com> writes:
Burton Radons wrote:
 Do you have a single instance of a static opCall being used for 
 construction where you don't just create a variable on the stack, build 
 it, and return it? Because that's all I've ever done. Hundreds of times. 
 Nothing else, and it just ends up being useless fluff making code harder 
 to read than it should be.
Given: auto s = S(1,2,3); I'm not sure what you mean?
Dec 09 2006
prev sibling next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:elfg41$139h$1 digitaldaemon.com...

 I've found they work well.

 And are they _really_ equivalent?
Not perfectly, but the differences are small.
 Where's the 'this' pointer in a static opCall()?
It's in your return value.
Okay, this obviously isn't working. Let's go at this from another angle. What is the TRUE reason you don't want to give structs ctors? I don't want to know why static opCalls are good, but why ctors are bad.
Dec 09 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Jarrett Billingsley wrote:
 What is the TRUE reason you don't want to give structs ctors?  I don't want 
 to know why static opCalls are good, but why ctors are bad. 
Constructor: S(v); static opCall: S(v) What's the difference? I just don't see the point for adding constructors.
Dec 09 2006
next sibling parent reply Burton Radons <burton-radons smocky.com> writes:
Walter Bright wrote:
 Jarrett Billingsley wrote:
 What is the TRUE reason you don't want to give structs ctors?  I don't 
 want to know why static opCalls are good, but why ctors are bad. 
Constructor: S(v); static opCall: S(v) What's the difference? I just don't see the point for adding constructors.
Here's the difference: struct S { this (int x) { w = calculate_something (x); } this (int x, int y) { this (x); z = calculate (y); } } struct S { static S opCall (int x) { S result; result.build (x); return result; } static S opCall (int x, int y) { S result; result.build (x, y); return result; } private void build (int x) { w = calculate (x); } private void build (int x, int y) { build (x); z = calculate (y); } } Look at all that waste! More than 50% of the code there is just restating things. This was much harder to write, is harder to maintain, and has granted us no benefits whatsoever.
Dec 09 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Burton Radons wrote:
 Here's the difference:
 
     struct S
     {
         this (int x)
         {
             w = calculate_something (x);
         }
 
         this (int x, int y)
         {
             this (x);
             z = calculate (y);
         }
     }
struct S { static S opCall(int x) { S result; result.w = calculate_something(x); return result; } static S opCall(int x, int y) { auto result = S(x); result.z = calculate(y); return result; } } It's 3 more lines of code. The two styles almost completely overlap, and since the latter is already in use, adding ctors just seems redundant.
Dec 09 2006
next sibling parent Lars Ivar Igesund <larsivar igesund.net> writes:
Walter Bright wrote:

 Burton Radons wrote:
 Here's the difference:
 
     struct S
     {
         this (int x)
         {
             w = calculate_something (x);
         }
 
         this (int x, int y)
         {
             this (x);
             z = calculate (y);
         }
     }
struct S { static S opCall(int x) { S result; result.w = calculate_something(x); return result; } static S opCall(int x, int y) { auto result = S(x); result.z = calculate(y); return result; } } It's 3 more lines of code. The two styles almost completely overlap, and since the latter is already in use, adding ctors just seems redundant.
Struct can have opCall, which is why they were used as a workaround for ctors. This was almost ok, but creating language support (through statement rewriting) for a workaround sounds a very bad thing to add prior to 1.0. This is certainly controversial enough, to think over for more than 2 weeks. I personally agree with those who want struct ctors. -- Lars Ivar Igesund blog at http://larsivi.net DSource & #D: larsivi
Dec 10 2006
prev sibling parent Chad J <gamerChad _spamIsBad_gmail.com> writes:
Walter Bright wrote:
 Burton Radons wrote:
 
 Here's the difference:

     struct S
     {
         this (int x)
         {
             w = calculate_something (x);
         }

         this (int x, int y)
         {
             this (x);
             z = calculate (y);
         }
     }
struct S { static S opCall(int x) { S result; result.w = calculate_something(x); return result; } static S opCall(int x, int y) { auto result = S(x); result.z = calculate(y); return result; } } It's 3 more lines of code. The two styles almost completely overlap, and since the latter is already in use, adding ctors just seems redundant.
OK here are the things that make me want constructors instead of static opCall: It takes a lot longer to type. You say only 3 more lines, but in that example 9 lines of constructor code becomes 12 lines of opCall code. The resulting code that is specific to these features is 25% fluff, and that includes the trivial curly braces. Character wise it is worse. The typing doesn't bug me as much as this though: what if the struct's name changes? And what if the opCall is heavily overloaded when the name changes? It's more unneeded code refactoring. That is one reason D constructors are so cool, and it kinda sucks that structs don't have that too. Also, static opCall is almost always used in the same way as a constructor. I'd expect them to have the same syntax, but they don't. I think this, and some of the above reaons, result in the workaround perception - everyone expects the smooth 'this' syntax, but get static opCall instead. Now I read that you would like to keep the semantic differences intact because they are useful. Forcing the possibility of a bitwise copy of the struct before it is unleashed will apparently allow for cool stuff. I don't think it's too unreasonable to have the 'this' identifier be a value rather than a reference in a struct constructor. Thus you have a function that implicitly creates a blank instance of the struct, then allows the programmer to modify it via 'this', and implicitly returns the 'this' struct as static opCall would.
Dec 11 2006
prev sibling next sibling parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:elfreq$1dvs$1 digitaldaemon.com...
 Constructor:

 S(v);

 static opCall:

 S(v)

 What's the difference? I just don't see the point for adding constructors.
In addition to what Burton posted, class: class C { this() { } } Struct: struct S { static S opCall() { S s; return s; } } It's completely un-orthogonal. Wouldn't it be so much cleaner and make so much more sense to allow ctors for structs? Isn't this the _purpose_ of ctors, to initialize members to useful values? Why have static opCall mean something completely different for structs and classes?
Dec 09 2006
prev sibling next sibling parent reply Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
Walter Bright wrote:
 Jarrett Billingsley wrote:
 What is the TRUE reason you don't want to give structs ctors?  I don't 
 want to know why static opCalls are good, but why ctors are bad. 
Constructor: S(v); static opCall: S(v) What's the difference? I just don't see the point for adding constructors.
The difference is that a ctor initializes an already-existing instance, while static opCall returns one, which is then copied. A static opCall won't handle the following case: ---- void delegate() globalDG; struct Foo { void func() { printf("myVal = %d\n", myVal); } static Foo opCall(int v) { Foo res; res.myVal = v; globalDG = &res.func; return res; } int myVal; } void main() { Foo f = Foo(5); globalDG(); // prints garbage instead of '5' } ---- The Foo instance returned from static opCall is copied, thus the 'ctor hack' doesn't have real access to the object it's constructing, not to mention the overhead of copying the struct to another place on stack... Also, new Foo(5) isn't going to work without real ctors or more hacks. -- Tomasz Stachowiak
Dec 09 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
Tom S wrote:
 A static opCall 
 won't handle the following case:
 
 ----
 
 void delegate() globalDG;
 
 
 struct Foo {
     void func() {
         printf("myVal = %d\n", myVal);
     }
 
     static Foo opCall(int v) {
         Foo res;
         res.myVal = v;
         globalDG = &res.func;
         return res;
     }
 
     int myVal;
 }
 
 
 void main() {
     Foo f = Foo(5);
     globalDG();  // prints garbage instead of '5'
 }
 
 ----
Ok, you're right on that one. But I am going to argue on philosophical grounds that this kind of code is incorrect for a couple of reasons: 1) constructors should construct objects, not set state outside themselves. 2) trying to keep track of every struct created is going to put a hole below the waterline in some new capabilities planned for the future. The compiler needs to be able to willy-nilly create bit copies of structs for this to work. C++ has this problem in spades (just follow the skin-rending and hair-pulling going on with the "move constructor" issues). Allowing the compiler to be able to create bit copied temporaries without needing to call constructors is going to be worthwhile by enabling some pretty cool stuff. I'm going to put forward the idea that this kind of thing should be done with classes, not structs.
 The Foo instance returned from static opCall is copied, thus the 'ctor 
 hack' doesn't have real access to the object it's constructing, not to 
 mention the overhead of copying the struct to another place on stack...
It's time to put the recurring efficiency argument to bed. Consider this D code: ------------------- struct S { static S opCall(int v) { S result; result.v = v; return result; } int v; } int test() { auto s = S(5); return s.v; } ------------compiles to---------- _D4test1S6opCallFiZS4test1S comdat ret _D4test4testFZi comdat mov EAX,5 ret --------------------------------- It doesn't get any better than that.
 Also,  new Foo(5)  isn't going to work without real ctors or more hacks.
That can be supported without any more work than supporting it with ctors.
Dec 09 2006
next sibling parent reply Don Clugston <dac nospam.com.au> writes:
Walter Bright wrote:
 2) trying to keep track of every struct created is going to put a hole 
 below the waterline in some new capabilities planned for the future. The 
 compiler needs to be able to willy-nilly create bit copies of structs 
 for this to work. C++ has this problem in spades (just follow the 
 skin-rending and hair-pulling going on with the "move constructor" 
 issues). Allowing the compiler to be able to create bit copied 
 temporaries without needing to call constructors is going to be 
 worthwhile by enabling some pretty cool stuff.
Intriguing. Evidently the ideas barrel is far from empty. <g>
Dec 10 2006
parent Walter Bright <newshound digitalmars.com> writes:
Don Clugston wrote:
 Intriguing. Evidently the ideas barrel is far from empty. <g>
No chance of that happening <g>.
Dec 13 2006
prev sibling next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Walter Bright wrote:
 It's time to put the recurring efficiency argument to bed. Consider this 
 D code:
That is a relief to know. What about heap construction? I think currently with the opCall approach you have to do something like: auto x = new Struct; *x = Struct(args); Does that copy get optimized away too? There are other issues people are concerned about also. Maybe it does only take two extra lines to define a static opCall vs a constructor, but that extra two lines is basically redundant noise and makes static opCall look hackish. And given that most every struct is going to need a constructor, it is hackish noise that we're all going to have to (and already do) see a lot. Also the name "static opCall" doesn't exactly scream out "i'm a constructor". And there's no guarantee that if I'm given a random struct I'll be able to use StructName() as a constructor. Users are free to do whatever they want with static opCall. With a constructor you have to construct. Just like you said "constructors should construct objects" -- ok, but static opCall is not intrinsically a constructor, any more than a function called make() is a constructor. It might be used as a constructor, or it might not. There's also the consistency issue, that if construction is done with 'this()' for classes it should be done with 'this()' for structs. I can kind of see why you don't want to use this() because in classes that's triggered by using the 'new' keyword, and for stack construct structs it makes sense to not use 'new'. But then if structs can be value constructed with x = Struct(), then why not classes too? But classes have to be value constructed using 'scope'. Ok, what if you just sidestep all that and introduce something like opCreate for structs. Basically this would be identical to the current "static opCall", but you could write the code for it just like a constructor. Like so: opCreate(int arg) { m_member = arg; } I.e. you don't specify return type, return value, or 'static' etc. It works just like a constructor, but you call it like static opCall. auto x = MyStruct() It would be an error to have both a static opCall and an opCreate. Of course at that point it really raises the question "why not just call it 'this' instead of opCreate". And declare that for structs you can invoke 'this' by using x = MyStruct(). Anyway, I really don't see why it has to be an either/or situation. Let people use this/opCreate for new code, and let them continue to use static opCall in old code. But make it an error to have both in a struct. What's so hard about that? --bb
Dec 10 2006
parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Bill Baxter wrote:
 There's also the consistency issue, that if construction is done with 
 'this()' for classes it should be done with 'this()' for structs.
 
One issue I have with allowing struct constructors is that the existence of struct constructors implies the existence of struct destructors. Do we really want to go that far? Though "static opCall" doesn't scream "constructor," it also doesn't seem to imply the user could define a destructor. I submit that "this()" would. -- Kirk McDonald Pyd: Wrapping Python with D http://pyd.dsource.org
Dec 10 2006
next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Kirk McDonald wrote:
 Bill Baxter wrote:
 There's also the consistency issue, that if construction is done with 
 'this()' for classes it should be done with 'this()' for structs.
One issue I have with allowing struct constructors is that the existence of struct constructors implies the existence of struct destructors. Do we really want to go that far? Though "static opCall" doesn't scream "constructor," it also doesn't seem to imply the user could define a destructor. I submit that "this()" would.
That seems like a non-issue to me. Even if people do take that to mean you can have a destructor, the compiler will crush any such delusions the moment you try to compile a struct with ~this in it. The compiler error message could even be "Structs cannot have destructors, only classes can". That should be pretty clear. Structs and classes are different in D. That's a fact, and new users are going to have to deal with it sooner or later. Should the language emphasize the similarities or stress the differences? I don't think it makes a big difference in this case. What's more important I think is that core operations can be performed in a way that is clear and unambiguous. No matter how you slice it, construction is a very common operation, and 'static opCall' does not offer a clear syntax for construction behavior, nor is it unambiguous in the sense that it can be used for any purpose the user desires. The fact that everyone was convinced that it must be inefficient because it *looks* like it's going to do extra copies is just another side effect of it using unambiguous constructor-like syntax. Walter went out of his way to avoid "looking hackish" with regard to foreach loops. It seems very odd to just accept hackish-looking constructors for structs. --bb
Dec 10 2006
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Bill Baxter wrote:

edit:
 The fact that everyone was convinced that it must be inefficient because it 
 *looks* like it's going to do extra copies is just another side effect 
 of it using unambiguous constructor-like syntax.
I mean "a side effect of it *not* using unambiguous constructor-like syntax", of course. --bb
Dec 10 2006
prev sibling next sibling parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Kirk McDonald wrote:
 Bill Baxter wrote:
 
 There's also the consistency issue, that if construction is done with 
 'this()' for classes it should be done with 'this()' for structs.
One issue I have with allowing struct constructors is that the existence of struct constructors implies the existence of struct destructors. Do we really want to go that far? Though "static opCall" doesn't scream "constructor," it also doesn't seem to imply the user could define a destructor. I submit that "this()" would.
Who knows, maybe D will have destructors for structs one day :o). Andrei
Dec 10 2006
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Andrei Alexandrescu (See Website For Email)" 
<SeeWebsiteForEmail erdani.org> wrote in message 
news:elihab$1q98$1 digitaldaemon.com...

 Who knows, maybe D will have destructors for structs one day :o).

 Andrei
I can see it now: DMD 0.178 * Added destructors for structs. Big thanks to Andrei.
Dec 10 2006
parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Jarrett Billingsley" <kb3ctd2 yahoo.com> wrote in message 
news:eliilm$1sk3$1 digitaldaemon.com...

 I can see it now:

 DMD 0.178
 * Added destructors for structs.  Big thanks to Andrei.
You can't see it, but there's a ";)" at the end of that post.
Dec 10 2006
prev sibling parent Boris Kolar <boris.kolar globera.com> writes:
== Quote from Kirk McDonald (kirklin.mcdonald gmail.com)'s article
 Bill Baxter wrote:
 One issue I have with allowing struct constructors is that the existence
 of struct constructors implies the existence of struct destructors. Do
 we really want to go that far?
I certainly do :) Since struct types are always known at compile-time, structs without destructors should not be affected. I think struct destructors will be very useful (RIAA, custom allocation/deallocation strategies).
Dec 11 2006
prev sibling parent reply BCS <BCS pathlink.com> writes:
Walter Bright wrote:
 Tom S wrote:
 The Foo instance returned from static opCall is copied, thus the 'ctor 
 hack' doesn't have real access to the object it's constructing, not to 
 mention the overhead of copying the struct to another place on stack...
It's time to put the recurring efficiency argument to bed. Consider this D code:
[...] In many cases, it may be vary good. However consider the case where there is more than one return statement using different variables. You now need a much better optimizer to get this optimized down to "almost nothing". Furthermore, it doesn't reflect what is actually being done. struct S { static S err; int k, l; static S opCall(int i, int j) { S ret; ret.k=i; ret.l=j; if(ret.test) return ret; else ret err; } bool test(){...} } With constructors, it is not only simpler code, but looks like what is happening. struct S { static S err; int k, l; this(int i, int j) { k=i; l=j; if(!ret.test) this = err; } bool test(){...} }
Dec 11 2006
parent reply Walter Bright <newshound digitalmars.com> writes:
BCS wrote:
 With constructors, it is not only simpler code, but looks like what is 
 happening.
 
 struct S
 {
     static S err;
     int k, l;
 
     this(int i, int j)
     {
         k=i;
         l=j;
 
         if(!ret.test) this = err;
     }
 
     bool test(){...}
 }
Assignment to this inside a constructor is a mistake as it breaks the assumptions the language makes about constructors.
Dec 13 2006
parent reply BCS <BCS pathlink.com> writes:
Walter Bright wrote:
 BCS wrote:
 
 With constructors, it is not only simpler code, but looks like what is 
 happening.

 struct S
 {
     static S err;
     int k, l;

     this(int i, int j)
     {
         k=i;
         l=j;

         if(!ret.test) this = err;
     }

     bool test(){...}
 }
Assignment to this inside a constructor is a mistake as it breaks the assumptions the language makes about constructors.
What assumptions does it break? This would be valid: struct S { static S err; int k, l; this(int i, int j) { k=i; l=j; if(!ret.test) { this.k = err.k; this.l = err.l; } } bool test(){...} } and as far as I can tell, they are the same. OK well maybe it should have been written as this: if(!ret.test) *this = err; // ^- add this Either way, I think the original argument still holds. The constructor form still looks more like what is acutely happening, and as a result has less of a "phantom" cost.
Dec 13 2006
parent Walter Bright <newshound digitalmars.com> writes:
BCS wrote:
 OK well maybe it should have been written as this:
 
         if(!ret.test) *this = err;
                    // ^- add this
That's completely different <g>.
 Either way, I think the original argument still holds. The constructor 
 form still looks more like what is acutely happening, and as a result 
 has less of a "phantom" cost.
Dec 14 2006
prev sibling parent Tom <ihate spam.com> writes:
Walter Bright escribió:
 Jarrett Billingsley wrote:
 What is the TRUE reason you don't want to give structs ctors?  I don't 
 want to know why static opCalls are good, but why ctors are bad. 
Constructor: S(v); static opCall: S(v) What's the difference? I just don't see the point for adding constructors.
I don't understand: the current way to go (opCall) *is a workaround*. That means, it's frequently used by D programmers to simulate something that is missing in the language (at least in my case). What about elegance (one of the most beloved properties of D)? Doesn't it account at all? Regards, -- Tom;
Dec 10 2006
prev sibling parent Boris Kolar <boris.kolar globera.com> writes:
== Quote from Walter Bright (newshound digitalmars.com)'s article
 Jarrett Billingsley wrote:
 It turns out that static opCall() and this() are equivalent. C++ has some
 obscure rules to try to disambiguate them. I wished to avoid that problem,
 and since static opCall() is routinely in use, picked them.
For classes they're not. Why should structs be any different? And even if static opCalls are routinely in use, are they the best solution?
I've found they work well.
Yes, but ctors would work even better: this(int a, int b) { _a = a; _b = b; } static Foo opCall(int a, int b) { Foo foo; foo._a = a; foo._b = b; return foo; } Suggestion: disallow both opCall and ctor for the same struct and there should be no ambiguity issues.
 That's implicit casting from a struct. It'll probably wind up getting
 supported in one form or another.
That's great :) It seems that some D issues suffer from "Black hole theory of languages": "C++ is a Black Hole: if you try to design something that's not C++, but like C++, you'll find that the gravitational forces on the design will suck it into the Black Hole, and it will become C++"
Dec 10 2006
prev sibling next sibling parent reply Brad Roberts <braddr puremagic.com> writes:
Walter Bright wrote:
 Jarrett Billingsley wrote:
 No offense, but would it honestly kill you to allow ctors in structs?  
 We've been using static opCall as a _workaround_ for the lack of 
 struct ctors, and making it part of the language doesn't really seem 
 to be addressing the problem.  That, and it doesn't make any sense 
 that classes use "this()" and structs use "static S opCall()".  We're 
 starting to get into "overloading indexing in C++" territory.
It turns out that static opCall() and this() are equivalent. C++ has some obscure rules to try to disambiguate them. I wished to avoid that problem, and since static opCall() is routinely in use, picked them.
Just because someone at some time in the distant pass realized that opCall() simulates constructor-like behavior, it doesn't make it 'ok'; it makes it 'an ok work around'. Think back over the last year.. how many people have asked how to create a constructor for structs? How much you wanna bet none of them would have had to ask if 'this' wasn't misspelled as 'opCall'? Since the desired behavior is construction, let's stop kidding ourselves and actually give structs both the behavior and the syntax of construction, please? And while you're in there.. how about destruction and RAII? I guess I'm in the camp that would like to see structs be closer to classes but am ok with them not supporting inheritance and other features that lead to vtables. Later, Brad
Dec 09 2006
parent reply Sean Kelly <sean f4.ca> writes:
Brad Roberts wrote:
 
 Since the desired behavior is construction, let's stop kidding ourselves 
 and actually give structs both the behavior and the syntax of 
 construction, please?  And while you're in there.. how about destruction 
 and RAII?
What ever happened to structs as aggregates? I thought this was their entire purpose for being in D. Adding ctors makes sense because it merely simplifies initialization, but adding dtors, copy semantics, and opAssign all seem bent on making structs into something they're not. After all, isn't that why we have classes? In fact, many of the instances where I was inclined to use structs simply to avoid the GC evaporated when stack construction of classes was added a few releases ago. Aside from the addition of a ctor, I'm quite happy to leave structs exactly as they were before 177. That said, I suppose I can appreciate the desire to give classes opAssign (even though it's somewhat weird), and perhaps the operator was added to structs simply for the sake of consistency. Sean
Dec 11 2006
next sibling parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Sean Kelly wrote:
 Brad Roberts wrote:
 
 Since the desired behavior is construction, let's stop kidding 
 ourselves and actually give structs both the behavior and the syntax 
 of construction, please?  And while you're in there.. how about 
 destruction and RAII?
What ever happened to structs as aggregates? I thought this was their entire purpose for being in D. Adding ctors makes sense because it merely simplifies initialization, but adding dtors, copy semantics, and opAssign all seem bent on making structs into something they're not. After all, isn't that why we have classes? In fact, many of the instances where I was inclined to use structs simply to avoid the GC evaporated when stack construction of classes was added a few releases ago. Aside from the addition of a ctor, I'm quite happy to leave structs exactly as they were before 177. That said, I suppose I can appreciate the desire to give classes opAssign (even though it's somewhat weird), and perhaps the operator was added to structs simply for the sake of consistency.
Classes are different from structs in two essential ways: 1. Polymorphism 2. Referential semantics The two are actually interdependent, as you can't have polymorphism comfortably unless you have reference semantics. That's the important distinction. Other than that, it's good that they share a number of valuable properties. Take private state for example. structs as sheer unchecked aggregates would provide too little value to be useful. Most of the time, aggregating some state together (date, time, etc.) comes together with some desirable invariant that the aggregate should hold, meaning that not all possible bit patterns of the aggregate can be meaningful. So it's useful that struct has private state. Consequently, they should have member functions (setters, getters) and the such. They also should have constructors so they can put themselves in a meaningful initial state, and should have opAssign so they allow controlled overwriting of their state. (It's an accident that opAssign from the same type has not been implemented yet; it can be safely allowed.) On the other hand, intercepting _duplication_ of structs naively would be a major breach in the object model, because it would frontally collide with (2) above: structs are values and the compiler can move and copy structs around discretionary using bitwise copying. That's why D currently does not allow interception of copy construction and destruction. It might in the future, but in a way that does not clash with the object model. (That is doable.) So my point was, as attractive the simple mantra "structs should be aggregates" is, it turns out it's not that useful. So it's great that D supports efficient and safe user-defined values. Andrei
Dec 11 2006
next sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Sean Kelly wrote:
 Brad Roberts wrote:

 Since the desired behavior is construction, let's stop kidding 
 ourselves and actually give structs both the behavior and the syntax 
 of construction, please?  And while you're in there.. how about 
 destruction and RAII?
What ever happened to structs as aggregates? I thought this was their entire purpose for being in D. Adding ctors makes sense because it merely simplifies initialization, but adding dtors, copy semantics, and opAssign all seem bent on making structs into something they're not. After all, isn't that why we have classes? In fact, many of the instances where I was inclined to use structs simply to avoid the GC evaporated when stack construction of classes was added a few releases ago. Aside from the addition of a ctor, I'm quite happy to leave structs exactly as they were before 177. That said, I suppose I can appreciate the desire to give classes opAssign (even though it's somewhat weird), and perhaps the operator was added to structs simply for the sake of consistency.
Classes are different from structs in two essential ways: 1. Polymorphism 2. Referential semantics The two are actually interdependent, as you can't have polymorphism comfortably unless you have reference semantics. That's the important distinction. Other than that, it's good that they share a number of valuable properties. Take private state for example. structs as sheer unchecked aggregates would provide too little value to be useful. Most of the time, aggregating some state together (date, time, etc.) comes together with some desirable invariant that the aggregate should hold, meaning that not all possible bit patterns of the aggregate can be meaningful. So it's useful that struct has private state. Consequently, they should have member functions (setters, getters) and the such. They also should have constructors so they can put themselves in a meaningful initial state, and should have opAssign so they allow controlled overwriting of their state. (It's an accident that opAssign from the same type has not been implemented yet; it can be safely allowed.)
"allow controlled overwriting of their state" -> Would that be for the case where the struct's private state (aka abstract state) is not just the bit pattern value of the struct, but also the contents of pointer/reference members? -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Dec 11 2006
parent reply "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Bruno Medeiros wrote:
 "allow controlled overwriting of their state" -> Would that be for the 
 case where the struct's private state (aka abstract state) is not just 
 the bit pattern value of the struct, but also the contents of 
 pointer/reference members?
That too. The canonical example is simpler - reject invalid attempts at setting state. Consider: struct BoundedInt(int min, int max) { ... } alias BoundedInt!(0, 100) Percent; Percent soFar = 0; ... soFar = bytesCopied * 100 / bytesToCopy; Upon assignment, the bounded int structure should do a runtime check to make sure it is being set to a number within bounds. Andrei
Dec 11 2006
parent Felix T <noemail noemail.com> writes:
To Andrei Alexandrescu:

 - Voiam doar sa salut prezenta dvs aici.
 - I greet your presence here.
Dec 11 2006
prev sibling next sibling parent Sean Kelly <sean f4.ca> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 
 Classes are different from structs in two essential ways:
 
 1. Polymorphism
 2. Referential semantics
 
 The two are actually interdependent, as you can't have polymorphism
 comfortably unless you have reference semantics.
Certainly.
 That's the important distinction. Other than that, it's good that they
 share a number of valuable properties. Take private state for example.
 structs as sheer unchecked aggregates would provide too little value to
 be useful. Most of the time, aggregating some state together (date,
 time, etc.) comes together with some desirable invariant that the 
 aggregate should hold, meaning that not all possible bit patterns of the 
 aggregate can be meaningful. So it's useful that struct has private 
 state. Consequently, they should have member functions (setters, 
 getters) and the such. They also should have constructors so they can 
 put themselves in a meaningful initial state, and should have opAssign 
 so they allow controlled overwriting of their state. (It's an accident 
 that opAssign from the same type has not been implemented yet; it can be 
 safely allowed.)
Point made. And I suppose opAssign isn't truly linked to the idea of object copy semantics--I've just gotten used to associating the two because of my experience with C++.
 So my point was, as attractive the simple mantra "structs should be
 aggregates" is, it turns out it's not that useful. So it's great that D
 supports efficient and safe user-defined values.
structs as aggregates do come in quite handy for a few specific situations, but I agree that they aren't tremendously useful in the general sense. It's beginning to seem, then, that common value types in D will be represented as structs, with classes reserved for those types with copy requirements. Once implicit cast support is added, it seems we'll be in pretty good shape for UDTs in D. And here I'd come to accept that we'd never have them :-) Sean
Dec 11 2006
prev sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Classes are different from structs in two essential ways:
 
 1. Polymorphism
 2. Referential semantics
 
 The two are actually interdependent, as you can't have polymorphism
 comfortably unless you have reference semantics.
That's one of the things I felt in my bones, but was unable to put my finger on it.
Dec 12 2006
next sibling parent reply Boris Kolar <boris.kolar globera.com> writes:
== Quote from Walter Bright (newshound digitalmars.com)'s article
 Andrei Alexandrescu (See Website For Email) wrote:
 Classes are different from structs in two essential ways:

 1. Polymorphism
 2. Referential semantics

 The two are actually interdependent, as you can't have polymorphism
 comfortably unless you have reference semantics.
That's one of the things I felt in my bones, but was unable to put my finger on it.
Actually, polymorphism does not imply referential semantics (nor does referential semantics imply polymorphism, of course). 1) Value sematics can be achieved with references as well, by using copy-on-write strategy (or by making classes immutable). All that without sacrificing polymorphism. 2) Polymorphism can be achieved without references as well, but then the size of struct could no longer be determined at comile-time. In other words, function 'sizeof' could no longer be parameterless. Or, alternatively, every such 'polymorphic struct' would have to contain two pointers: VMT and additional 'data' pointer (which is actually pretty close to using references anyway). I'm in favour of allowing programmer to mark special semantics, such as value class, or pure function (without side effects). Many additional optimizations can be performed under such assumptions. Most notably: value classes could be destroyed predictably (without causing arbitrary long GC pauses) and calls to pure functions with unchanged parameters can be cached (for example, foo.name().length()...). Also, it would be interesting to mark some functions as compile-time, requiring that they only interact with values that are known in compile-time. This would make metaprogramming much more similar to normal programming. One additional nice thing: you can support such declarations even before optimizer takes advantage of them. Also, I would suggest issuing a compile-time warning when polymorphism is used without explicit 'override' directive. Perhaps coupled with a pragma or something to turn such warning off. PS: I really appreciate all the work that has been put in D. D is going to be a much needed successor of C++. I don't see D as competitor to be a terrible choice), but it sure can rock the non-VM world!
Dec 12 2006
next sibling parent reply Ivan Senji <ivan.senji gmail.com> writes:
Boris Kolar wrote:
 PS: I really appreciate all the work that has been put in D. D is going
 to be a much needed successor of C++. I don't see D as competitor to

 be a terrible choice), but it sure can rock the non-VM world!
Why not? I think that D could rock the .NET world too. I always wished (and still do) for a d.net implementation.
Dec 12 2006
parent reply Alexander Panek <a.panek brainsware.org> writes:
Ivan Senji wrote:
 Boris Kolar wrote:
 PS: I really appreciate all the work that has been put in D. D is going
 to be a much needed successor of C++. I don't see D as competitor to

 be a terrible choice), but it sure can rock the non-VM world!
Why not? I think that D could rock the .NET world too. I always wished (and still do) for a d.net implementation.
.NET is not the solution for all problems. There will be other library approaches that maybe even perform better and use features of D that make it like .NET on steroids. I bet after some time of an official, fixed D 1.0 specification, there will some libraries pop up that may serve needs like .NET or J2EE and what not do, also. Apart from that.. hey, you're posting this in the official D newsgroup ;) . Kind regards, Alex
Dec 12 2006
parent reply Ivan Senji <ivan.senji_REMOVE_ _THIS__gmail.com> writes:
Alexander Panek wrote:
 Ivan Senji wrote:
 Boris Kolar wrote:
 PS: I really appreciate all the work that has been put in D. D is going
 to be a much needed successor of C++. I don't see D as competitor to

 be a terrible choice), but it sure can rock the non-VM world!
Why not? I think that D could rock the .NET world too. I always wished (and still do) for a d.net implementation.
..NET is not the solution for all problems.
Naturally. That is why we are here (using D).
 There will be other library 
 approaches that maybe even perform better and use features of D that 
 make it like .NET on steroids. I bet after some time of an official, 
 fixed D 1.0 specification, there will some libraries pop up that may 
 serve needs like .NET or J2EE and what not do, also.
I sure hope so. What I was trying to say programming in .NET is sometimes required and
 
 Apart from that.. hey, you're posting this in the official D newsgroup ;) .
Is that a problem? :)
Dec 12 2006
parent reply Alexander Panek <a.panek brainsware.org> writes:
Ivan Senji wrote:
 Alexander Panek wrote:
 Ivan Senji wrote:
 Boris Kolar wrote:
 PS: I really appreciate all the work that has been put in D. D is going
 to be a much needed successor of C++. I don't see D as competitor to

 be a terrible choice), but it sure can rock the non-VM world!
Why not? I think that D could rock the .NET world too. I always wished (and still do) for a d.net implementation.
..NET is not the solution for all problems.
Naturally. That is why we are here (using D).
Exacteley!
 
 There will be other library approaches that maybe even perform better 
 and use features of D that make it like .NET on steroids. I bet after 
 some time of an official, fixed D 1.0 specification, there will some 
 libraries pop up that may serve needs like .NET or J2EE and what not 
 do, also.
I sure hope so. What I was trying to say programming in .NET is sometimes required and
Are you, actually, talking about a D -> MSIL compiler, so it can be run inside the VM and a .NET implementation in D, for that purpose? Or do like that has to be in D. :) I hope this will pop up
 Apart from that.. hey, you're posting this in the official D newsgroup 
 ;) .
Is that a problem? :)
Of course not. Was just a side-kick regarding your "I want .NET in D" statement :) . Kind regards, Alex
Dec 12 2006
next sibling parent Ivan Senji <ivan.senji gmail.com> writes:
Alexander Panek wrote:
 Ivan Senji wrote:
 I sure hope so.
 What I was trying to say programming in .NET is sometimes required and

Are you, actually, talking about a D -> MSIL compiler, so it can be run inside the VM and a .NET implementation in D, for that purpose? Or do like that has to be in D. :) I hope this will pop up
Actually I really was talking about a D->MSIL compiler for those cases when you just have to use .net and would still prefer other benefits of D. But the other thing would be great too.
 
 Apart from that.. hey, you're posting this in the official D
 newsgroup ;) .
Is that a problem? :)
Of course not. Was just a side-kick regarding your "I want .NET in D" statement :) .
:P
Dec 12 2006
prev sibling parent reply "Lionello Lunesu" <lionello lunesu.remove.com> writes:
"Alexander Panek" <a.panek brainsware.org> wrote in message 
news:eln5td$2pta$1 digitaldaemon.com...
 Are you, actually, talking about a D -> MSIL compiler, so it can be run 
 inside the VM and a .NET implementation in D, for that purpose? Or do you 

 has to be in D. :) I hope this will pop up
We had one, once : ( but then the guy's HDD crashed... Sad, sad story. L.
Dec 13 2006
parent reply Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Lionello Lunesu wrote:
 "Alexander Panek" <a.panek brainsware.org> wrote in message 
 news:eln5td$2pta$1 digitaldaemon.com...
 Are you, actually, talking about a D -> MSIL compiler, so it can be run 
 inside the VM and a .NET implementation in D, for that purpose? Or do you 

 has to be in D. :) I hope this will pop up
We had one, once : ( but then the guy's HDD crashed... Sad, sad story. L.
https://mywebspace.wisc.edu/daaugustine/web/d/ This is all that remains of all his work, so far as I can tell. Some day it would be nice if http://www.scratch-ware.net/dfiles/ suddenly existed again. Oh well. -- Chris Nicholson-Sauls
Dec 14 2006
parent reply Justin C Calvarese <technocrat7 gmail.com> writes:
Chris Nicholson-Sauls wrote:
 Lionello Lunesu wrote:
 "Alexander Panek" <a.panek brainsware.org> wrote in message 
 news:eln5td$2pta$1 digitaldaemon.com...
 Are you, actually, talking about a D -> MSIL compiler, so it can be 
 run inside the VM and a .NET implementation in D, for that purpose? 

 Something like that has to be in D. :) I hope this will pop up
We had one, once : ( but then the guy's HDD crashed... Sad, sad story. L.
https://mywebspace.wisc.edu/daaugustine/web/d/ This is all that remains of all his work, so far as I can tell. Some day it would be nice if http://www.scratch-ware.net/dfiles/ suddenly existed again. Oh well. -- Chris Nicholson-Sauls
I think I have pretty much all of the binary files he released (though they're archived on some CD in my closet), but he never released any source before his hard drive crashed, so I don't have any of that. And I think the binaries targeted some beta version of .NET and required .dll's that you'd only have if you installed a beta version of Visual Studio. Seemed like a cool idea, but it took a lot of effort just to try it out... -- jcc7
Dec 14 2006
parent reply Lionello Lunesu <lio lunesu.remove.com> writes:
Justin C Calvarese wrote:
 Chris Nicholson-Sauls wrote:
 Lionello Lunesu wrote:
 "Alexander Panek" <a.panek brainsware.org> wrote in message 
 news:eln5td$2pta$1 digitaldaemon.com...
 Are you, actually, talking about a D -> MSIL compiler, so it can be 
 run inside the VM and a .NET implementation in D, for that purpose? 

 Something like that has to be in D. :) I hope this will pop up
We had one, once : ( but then the guy's HDD crashed... Sad, sad story. L.
https://mywebspace.wisc.edu/daaugustine/web/d/ This is all that remains of all his work, so far as I can tell. Some day it would be nice if http://www.scratch-ware.net/dfiles/ suddenly existed again. Oh well. -- Chris Nicholson-Sauls
I think I have pretty much all of the binary files he released (though they're archived on some CD in my closet), but he never released any source before his hard drive crashed, so I don't have any of that. And I think the binaries targeted some beta version of .NET and required .dll's that you'd only have if you installed a beta version of Visual Studio.
Can't assemblies be fairly easily disassembled? L.
Dec 15 2006
parent Sean Kelly <sean f4.ca> writes:
Lionello Lunesu wrote:
 Justin C Calvarese wrote:
 Chris Nicholson-Sauls wrote:
 Lionello Lunesu wrote:
 "Alexander Panek" <a.panek brainsware.org> wrote in message 
 news:eln5td$2pta$1 digitaldaemon.com...
 Are you, actually, talking about a D -> MSIL compiler, so it can be 
 run inside the VM and a .NET implementation in D, for that purpose? 

 Something like that has to be in D. :) I hope this will pop up
We had one, once : ( but then the guy's HDD crashed... Sad, sad story. L.
https://mywebspace.wisc.edu/daaugustine/web/d/ This is all that remains of all his work, so far as I can tell. Some day it would be nice if http://www.scratch-ware.net/dfiles/ suddenly existed again. Oh well. -- Chris Nicholson-Sauls
I think I have pretty much all of the binary files he released (though they're archived on some CD in my closet), but he never released any source before his hard drive crashed, so I don't have any of that. And I think the binaries targeted some beta version of .NET and required .dll's that you'd only have if you installed a beta version of Visual Studio.
Can't assemblies be fairly easily disassembled?
Yes. In fact, the times I've done it I've seen an exact copy of the original source code (!). Try Reflector from this link: http://www.aisto.com/roeder/dotnet/ Sean
Dec 15 2006
prev sibling parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Boris Kolar wrote:
 == Quote from Walter Bright (newshound digitalmars.com)'s article
 
Andrei Alexandrescu (See Website For Email) wrote:

Classes are different from structs in two essential ways:

1. Polymorphism
2. Referential semantics

The two are actually interdependent, as you can't have polymorphism
comfortably unless you have reference semantics.
That's one of the things I felt in my bones, but was unable to put my finger on it.
Actually, polymorphism does not imply referential semantics (nor does referential semantics imply polymorphism, of course). 1) Value sematics can be achieved with references as well, by using copy-on-write strategy (or by making classes immutable). All that without sacrificing polymorphism.
Of course. It's a "true but uninteresting" fact. Even in current D you can think that int is a reference and that ++i rebinds i to another value. But when talking about reference semantics, that means object is unique, references are many. Doing copy-on-write takes reference-ness out of references, so of course then they start behaving like value.
 2) Polymorphism can be achieved without references as well, but then
 the size of struct could no longer be determined at comile-time. In
 other words, function 'sizeof' could no longer be parameterless. Or,
 alternatively, every such 'polymorphic struct' would have to contain
 two pointers: VMT and additional 'data' pointer (which is actually
 pretty close to using references anyway).
Yup, that's reference all right. Some Java implementations do this. Andrei
Dec 12 2006
prev sibling parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Walter Bright wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 
 Classes are different from structs in two essential ways:

 1. Polymorphism
 2. Referential semantics

 The two are actually interdependent, as you can't have polymorphism
 comfortably unless you have reference semantics.
That's one of the things I felt in my bones, but was unable to put my finger on it.
I feel it in my bones too. It hurts when it rains :o). Andrei
Dec 12 2006
prev sibling parent reply "John Reimer" <terminal.node gmail.com> writes:
On Mon, 11 Dec 2006 10:50:41 -0800, Sean Kelly <sean f4.ca> wrote:

 Brad Roberts wrote:
  Since the desired behavior is construction, let's stop kidding  =
 ourselves and actually give structs both the behavior and the syntax =
of =
 construction, please?  And while you're in there.. how about  =
 destruction and RAII?
What ever happened to structs as aggregates? I thought this was their=
=
 entire purpose for being in D.  Adding ctors makes sense because it  =
 merely simplifies initialization, but adding dtors, copy semantics, an=
d =
 opAssign all seem bent on making structs into something they're not.  =
 After all, isn't that why we have classes?  In fact, many of the  =
 instances where I was inclined to use structs simply to avoid the GC  =
 evaporated when stack construction of classes was added a few releases=
=
 ago.  Aside from the addition of a ctor, I'm quite happy to leave  =
 structs exactly as they were before 177.  That said, I suppose I can  =
 appreciate the desire to give classes opAssign (even though it's  =
 somewhat weird), and perhaps the operator was added to structs simply =
=
 for the sake of consistency.


 Sean
I used to be convinced that struct's needed constructors; but now I see = = that there is a steam-roll effect as the demand for one class-like = attribute is proposed: suddenly people want more class-related = functionality or people argue that there is a basis for additional = features for the sake of consistancy. Then the redesign of structs star= ts = taking on the appearance of a reegineered, specialized class -- somethin= g = of a "context switch" too since classes use reference semantics. Perhap= s = we should be careful about hurtling too far in the direction of C++? From the perspective of setting initial state, classes in the OOP conte= xt = use constructors to do that. Structs were never intended to have OOP li= ke = syntax (or so it seems to me). Why can't structs just use an initialize= r = like constants or statics? I notice that Walter has added the struct = initialization using S(x) syntax. I think it is somewhat strange, but I= = suppose that was designed to be an alternative to constructor = initialization? What happened to something like a struct literal = intializer (which only works for static structs and constant values)? W= hy = can't such initialization be extended to local structs? ... struct S { int i; bool b; } static S t =3D { 5, true }; // must be "static" or initialization won't= work ... The above only works for static Structs. But that kind of initializatio= n = seems to be more consistant with the imperitive style of structs verses = = the OOP style of classes (if only non-statics could be initialized too, = = that is). When all is said and done, this is just like doing the same as= = S( 5, 2 ) since 0.177. But I guess as soon as we start adding "methods" to the struct that are = = responsible for changing state in the struct, a whole new set of = principles comes to work. Adding constructor funtionality via "this()" = is = only one way to fix it. opCall, the current way, is a rather ugly = default. If it is important to keep structs distinct from classes, then= = structs need to adopt a alternate way of doing initialization (but pleas= e = not opCall). I don't think "this()" really is the optimal way, though = perhaps the most familiar due to the influence of classes. I think ther= e = should remain a strong distinction between class and struct. If, nevertheless, Walter decides to implement a "this" constructor for = struct, I really hope he doesn't feel the need to go with a destructor a= nd = more class-like functionality as well. Ironically, I feel more sympathetic to what appears to be Walter's opini= on = on the matter this time around. :D -JJR
Dec 11 2006
parent reply Sean Kelly <sean f4.ca> writes:
John Reimer wrote:
 
  From the perspective of setting initial state, classes in the OOP 
 context use constructors to do that.  Structs were never intended to 
 have OOP like syntax (or so it seems to me).  Why can't structs just use 
 an initializer like constants or statics?  I notice that Walter has 
 added the struct initialization using S(x) syntax.  I think it is 
 somewhat strange, but I suppose that was designed to be an alternative 
 to constructor initialization?   What happened to something like a 
 struct literal intializer (which only works for static structs and 
 constant values)?  Why can't such initialization be extended to local 
 structs?
 
 ....
 struct S
 {
    int i;
    bool b;
 }
 
 static S t = { 5, true };  // must be "static" or initialization won't work
I think the problem here is that such initializers would not apply to private data, and more to the point, a ctor can do more than simply assign a constant to each member of the struct. But it would be nice if this syntax worked for non-static structs anyway.
 But I guess as soon as we start adding "methods" to the struct that are 
 responsible for changing state in the struct, a whole new set of 
 principles comes to work.  Adding constructor funtionality via "this()" 
 is only one way to fix it.  opCall, the current way, is a rather ugly 
 default.  If it is important to keep structs distinct from classes, then 
 structs need to adopt a alternate way of doing initialization (but 
 please not opCall).  I don't think "this()" really is the optimal way, 
 though perhaps the most familiar due to the influence of classes.  I 
 think there should remain a strong distinction between class and struct.
As do I. And I suppose the presence or lack of polymorphism is a sufficient distinction--the underpinnings for polymorphism do have a fairly significant impact on how an object is represented internally, etc.
 If, nevertheless, Walter decides to implement a "this" constructor for 
 struct, I really hope he doesn't feel the need to go with a destructor 
 and more class-like functionality as well.
The idea of a dtor for structs doesn't make much sense to me because of the bit-copy semantics. When would the dtor for a struct used as a return value be called? Sean
Dec 11 2006
parent "John Reimer" <terminal.node gmail.com> writes:
On Mon, 11 Dec 2006 15:57:51 -0800, Sean Kelly <sean f4.ca> wrote:

 John Reimer wrote:
   From the perspective of setting initial state, classes in the OOP  =
 context use constructors to do that.  Structs were never intended to =
=
 have OOP like syntax (or so it seems to me).  Why can't structs just =
=
 use an initializer like constants or statics?  I notice that Walter h=
as =
 added the struct initialization using S(x) syntax.  I think it is  =
 somewhat strange, but I suppose that was designed to be an alternativ=
e =
 to constructor initialization?   What happened to something like a  =
 struct literal intializer (which only works for static structs and  =
 constant values)?  Why can't such initialization be extended to local=
=
 structs?
  ....
 struct S
 {
    int i;
    bool b;
 }
  static S t =3D { 5, true };  // must be "static" or initialization w=
on't =
 work
I think the problem here is that such initializers would not apply to =
=
 private data, and more to the point, a ctor can do more than simply  =
 assign a constant to each member of the struct.  But it would be nice =
if =
 this syntax worked for non-static structs anyway.
Ah true. But then, maybe there shouldn't be private data in a struct = either. :) I still see structs as a fairly basic non-OOP entity. -JJR
Dec 11 2006
prev sibling parent Marcin Kuszczak <aarti interia.pl> writes:
Walter Bright wrote:

 I'm also a little wary about opAssign.  Not that I don't find it useful,
 just that you've been vehemently opposed to overloading it in D for how
 many years, and three weeks before 1.0 you reverse your opinion.  What
 does that mean?
The problem I have, and still have, is the identity assignment. This is specifically disallowed with opAssign. I can go into the reasons why if you like.
I wonder if it is not possible to make D, working as a base with values not with references. In such a case references would be hidden from programmers and only values would be visible at first. Assigning would always mean assign value, and when there should be passed reference the special syntax would be necessary, so references would be special case. To disambiguate you can make something like this: auto v := new Any; // special assignment operator ':=' for references auto z := new Any; v = 5; // Standard opAssign z = v; // No problem here, opAssign can be used as usually z :=v; // Reference copy - opAssign is not used here Additionally when passing arguments to function, which demands references, there could be used implicit conversion from values to references (so that for user it would be visible as sending values). Below example almost works now (almost because constructors can not be templatized): void varFunc(Any[] arr ...) {         foreach(v; arr) writefln(v.type); } void main() { } In language in which everything is consider as an object you could just write: void main() { bool test; } If there should be send reference(pointer) it should be done explicitly. E.g. void varFunc(Any*[] arr ...) { .... } void main() { } Does it make any sense? Please comment...
 Having opAssign for foreign types turns out to be needed for things
 like, say I want to build a ranged integer. This is an integer that can
 only have values between m and n. Without opAssign, I'd have to use a
 property:
 RangedInt!(m,n) r;
 r.value = 6;
 It's just not right.
the same in case of boost::Any port to D. -- Regards Marcin Kuszczak (Aarti_pl)
Dec 09 2006
prev sibling next sibling parent Marcin Kuszczak <aarti interia.pl> writes:
Walter Bright wrote:

 More ABI changes, and implicit [] => * no longer allowed.
 
 http://www.digitalmars.com/d/changelog.html
 
 http://ftp.digitalmars.com/dmd.175.zip
opAssing not mentioned here, but anyway I like what I have read in Docs! Finally I can use more user friendly and compact syntax for assigning values to Any (http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D.announce&artnum=4827): auto v=new Any; Was: v.assign(5); Now: v=5; Very good syntax for this kind of container! I just miss templatized constructors and following will be available: void varFunc(Any[] arr ...) { foreach(v; arr) { writefln(v.type); } } void main() { bool test; } (Currently it is necessary to write: ) I will send updated version of Any soon... -- Regards Marcin Kuszczak (Aarti_pl)
Dec 09 2006
prev sibling next sibling parent reply Hasan Aljudy <hasan.aljudy gmail.com> writes:
Walter Bright wrote:
 More ABI changes, and implicit [] => * no longer allowed.
 
 http://www.digitalmars.com/d/changelog.html
 
 http://ftp.digitalmars.com/dmd.175.zip
Can you clarify more what that means? enable more thorough rtti. So, can we get a string version of enum values? and can we get struct names? Why is everyone talking about opAssign? I don't see it mentioned in the change log.
Dec 09 2006
parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Hasan Aljudy" <hasan.aljudy gmail.com> wrote in message 
news:elfcgr$v45$1 digitaldaemon.com...

 Why is everyone talking about opAssign? I don't see it mentioned in the 
 change log.
Have a look on the "Operator Overloading" page of the spec.
Dec 09 2006
prev sibling next sibling parent reply clayasaurus <clayasaurus gmail.com> writes:
Walter Bright wrote:
 More ABI changes, and implicit [] => * no longer allowed.
 
 http://www.digitalmars.com/d/changelog.html
 
 http://ftp.digitalmars.com/dmd.175.zip
I'm not sure if this is a bug or what, but to get derelict from not spitting out this error message... derelict\opengl\glu.obj(glu) Error 42: Symbol Undefined _D8derelict6opengl3glu8GLUnurbs6__initZ derelict\opengl\glu.obj(glu) Error 42: Symbol Undefined _D8derelict6opengl3glu10GLUquadric6__initZ derelict\opengl\glu.obj(glu) Error 42: Symbol Undefined _D8derelict6opengl3glu13GLUtesselator6__initZ In glu.d line 287-289 I had to change struct GLUnurbs; struct GLUquadric; struct GLUtesselator; to struct GLUnurbs{} struct GLUquadric{} struct GLUtesselator{} In order for the compiler to recognize the symbols. Thanks for the release. ~ Clay
Dec 09 2006
parent reply "Frank Benoit (keinfarbton)" <benoit tionex.removethispart.de> writes:
did you recompile linked D libs?
Dec 10 2006
parent clayasaurus <clayasaurus gmail.com> writes:
Frank Benoit (keinfarbton) wrote:
 did you recompile linked D libs?
Yes, I actually don't use any linked libs since D compiles so fast. I just let build pull in what it wants to.
Dec 10 2006
prev sibling next sibling parent reply "Lionello Lunesu" <lionello lunesu.remove.com> writes:
Walter, you forgot to change this line in the docs:

"The operators =, !, ., &&, ||, ?:, and a few others will likely never be 
overloadable. "
http://www.digitalmars.com/d/operatoroverloading.html

: )

L. 
Dec 10 2006
parent Walter Bright <newshound digitalmars.com> writes:
<g>
Dec 10 2006
prev sibling next sibling parent reply Samuel MV <samuel jxdesigner.com> writes:
Thank you, Walter.

D 1.0 is going to be THE BEST general purpose language ... :D
Dec 10 2006
parent Alexander Panek <a.panek brainsware.org> writes:
Samuel MV wrote:
 Thank you, Walter.
 
 D 1.0 is going to be THE BEST general purpose language ... :D
s/general purpose \(language\)/\1 EVER/ ...or such :P
Dec 10 2006
prev sibling next sibling parent reply Pragma <ericanderton yahoo.removeme.com> writes:
Walter Bright wrote:
 More ABI changes, and implicit [] => * no longer allowed.
 
 http://www.digitalmars.com/d/changelog.html
 
 http://ftp.digitalmars.com/dmd.175.zip
Walter, this is a great update. Especially the removal of the implicit cast for array pointers. But I'd like to echo the other comments in this thread regarding structs. IMO, we're not there yet. I think folks are looking for a solution that does this: - A ctor like syntax for creating a new struct - No more forced copy of the entire struct on creation - Something that is disambiguated from static opCall - Ctors that are as clear to read as this() in classes and modules. Again, thanks for the solid push to 1.0 this month. I'm sure we haven't seen anything yet. -- - EricAnderton at yahoo
Dec 11 2006
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Pragma wrote:
<snip>
 But I'd like to echo the other comments in this thread regarding 
 structs.  IMO, we're not there yet.  I think folks are looking for a 
 solution that does this:
 
 - A ctor like syntax for creating a new struct
 - No more forced copy of the entire struct on creation
What do you mean by this?
 - Something that is disambiguated from static opCall
<snip> Do you mean that constructors for structs should have a notation distinct from S(...)? Stewart.
Dec 11 2006
parent reply Pragma <ericanderton yahoo.removeme.com> writes:
Stewart Gordon wrote:
 Pragma wrote:
 <snip>
 But I'd like to echo the other comments in this thread regarding 
 structs.  IMO, we're not there yet.  I think folks are looking for a 
 solution that does this:

 - A ctor like syntax for creating a new struct
 - No more forced copy of the entire struct on creation
What do you mean by this?
I'm glad you asked. :) Static opCall() is not a ctor. It never was. People have been clamoring to be able to use this() inside of a struct, much like they can with classes and modules. But the desire here goes beyond mere symmetry between type definitions. The forced copy issue is something that is an artifact of emulating a constructor for a struct. Take the standard approach for example: struct Foo{ int a,b,c; } Foo f = {a:1, b:2, c:3}; Foo f = {1,2,3}; // more succinct version So here we create a struct in place, and break encapsulation in the process. What we really want is an opaque type, that has a little more smarts on creation. Taking advantage of in/body/out would be nice too. No problem, we'll just use opCall(): struct Foo{ int a,b,c; static Foo opCall(int a,int b,int c){ Foo _this; _this.a = a; _this.b = b; _this.c = c; return _this; } } Foo f = Foo(1,2,3); That's better, but look at what's really happening here. Inlining and compiler optimization aside, the 'constructor' here creates a Foo on the stack which is then returned and *copied* to the destination 'f'. To most, that won't ever seem like a problem. But for folks who are working with Vector types or Matrix implementations, that's something to scream about. In a nutshell, any struct wider than a register that is populated in the 100's to 1000's is wasting cycles needlessly. So that brings us to something like this: struct Foo{ int a,b,c; this(int a,int b,int c){ this.a = a; this.b = b; this.c = c; } } Foo f = Foo(1,2,3); Ambiguity aside, this fixes encapsulation, gives a familiar syntax, and almost fixes the allocation issues. (see below)
 
 - Something that is disambiguated from static opCall
<snip> Do you mean that constructors for structs should have a notation distinct from S(...)?
Well, I think it's one of the reasons why we don't have ctors for structs right now. The preferred syntax for a "struct ctor" would probably be this: S foo = S(a,b,c); Which is indistinct from "static opCall". Throwing 'new' in there wouldn't work either, since that would be a dynamic allocation: S* foo = new S(a,b,c); So that leaves us with "something else" that provides both a way to invoke a ctor, yet allocates the data on the stack and doesn't force you to create an additional copy: S foo(a,b,c); // c++ style S foo = stackalloc S(a,b,c); // alloca() style (in place of new) S foo = new(stack) S(a,b,c): // another idea -- - EricAnderton at yahoo
Dec 12 2006
next sibling parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Pragma" <ericanderton yahoo.removeme.com> wrote in message 
news:elmhtj$1qv7$1 digitaldaemon.com...

 Well, I think it's one of the reasons why we don't have ctors for structs 
 right now.  The preferred syntax for a "struct ctor" would probably be 
 this:

 S foo = S(a,b,c);

 Which is indistinct from "static opCall".  Throwing 'new' in there 
 wouldn't work either, since that would be a dynamic allocation:

 S* foo = new S(a,b,c);

 So that leaves us with "something else" that provides both a way to invoke 
 a ctor, yet allocates the data on the stack and doesn't force you to 
 create an additional copy:

 S foo(a,b,c);  // c++ style
 S foo = stackalloc S(a,b,c); // alloca() style (in place of new)
 S foo = new(stack) S(a,b,c): // another idea
Personally for in-place stack allocation, I like the C++ style the best. It's the simplest to deal with for the compiler (it's 100% obvious that it's being created on the stack and there's no need for optimization to take care of anything). And it makes sense -- it says "make this a local variable, but call the constructor on it as well." There's no assignment so it doesn't look like any copying is going on.
Dec 12 2006
prev sibling next sibling parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Pragma wrote:
 Foo f = Foo(1,2,3);
 
 That's better, but look at what's really happening here.  Inlining and 
 compiler optimization aside, the 'constructor' here creates a Foo on the 
 stack which is then returned and *copied* to the destination 'f'.
About this optimization business, is this an issue? Since Walter stated that such copies are optimized away (trivially?), my assumption was that the syntax as it is now relies on this optimization being present. Or to put it in other words, static opCall would not be supported if there was no such optimization possible. Perhaps it is similar to how the use of functors with templates in C++ rely on inlining, STL would be so slow without such optimizations. My question is if it is reasonable to make this assumption or can you put compiler optimization aside?
Dec 12 2006
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Lutger" <lutger.blijdestijn gmail.com> wrote in message 
news:elmjl8$1tdg$1 digitaldaemon.com...

 About this optimization business, is this an issue? Since Walter stated 
 that such copies are optimized away (trivially?), my assumption was that 
 the syntax as it is now relies on this optimization being present. Or to 
 put it in other words, static opCall would not be supported if there was 
 no such optimization possible.
 Perhaps it is similar to how the use of functors with templates in C++ 
 rely on inlining, STL would be so slow without such optimizations.

 My question is if it is reasonable to make this assumption or can you put 
 compiler optimization aside?
The impression I get from Walter is that _eeeevery_ compiler has optimization, so it's a nonissue. :P Optimization should be an entirely optional pass. Making language features rely on it seems hackish at best.
Dec 12 2006
next sibling parent Pragma <ericanderton yahoo.removeme.com> writes:
Jarrett Billingsley wrote:
 "Lutger" <lutger.blijdestijn gmail.com> wrote in message 
 news:elmjl8$1tdg$1 digitaldaemon.com...
 
 About this optimization business, is this an issue? Since Walter stated 
 that such copies are optimized away (trivially?), my assumption was that 
 the syntax as it is now relies on this optimization being present. Or to 
 put it in other words, static opCall would not be supported if there was 
 no such optimization possible.
 Perhaps it is similar to how the use of functors with templates in C++ 
 rely on inlining, STL would be so slow without such optimizations.

 My question is if it is reasonable to make this assumption or can you put 
 compiler optimization aside?
The impression I get from Walter is that _eeeevery_ compiler has optimization, so it's a nonissue. :P Optimization should be an entirely optional pass. Making language features rely on it seems hackish at best.
Exactly. Moreover, it's not always possible to inline or optimize even by a compiler that can do it well, so it *must* be optional by definition. Also there are some rather significant "edge cases" involved here. What about libraries, or reflection? -- - EricAnderton at yahoo
Dec 12 2006
prev sibling parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Jarrett Billingsley wrote:
 "Lutger" <lutger.blijdestijn gmail.com> wrote in message 
 news:elmjl8$1tdg$1 digitaldaemon.com...
 
 About this optimization business, is this an issue? Since Walter stated 
 that such copies are optimized away (trivially?), my assumption was that 
 the syntax as it is now relies on this optimization being present. Or to 
 put it in other words, static opCall would not be supported if there was 
 no such optimization possible.
 Perhaps it is similar to how the use of functors with templates in C++ 
 rely on inlining, STL would be so slow without such optimizations.

 My question is if it is reasonable to make this assumption or can you put 
 compiler optimization aside?
The impression I get from Walter is that _eeeevery_ compiler has optimization, so it's a nonissue. :P Optimization should be an entirely optional pass. Making language features rely on it seems hackish at best.
Can you explain why? 'Rely' in this context doesn't mean the language is broken right? It just means it is slower, but isn't that expected from a non-optimizing compiler anyway?
Dec 12 2006
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Lutger" <lutger.blijdestijn gmail.com> wrote in message 
news:elmkes$1ult$1 digitaldaemon.com...

 Can you explain why? 'Rely' in this context doesn't mean the language is 
 broken right? It just means it is slower, but isn't that expected from a 
 non-optimizing compiler anyway?
Yes, I guess that's true. But if a simple addition i.e. x = a + b; Compiled to mov _TEMP1, a mov _TEMP2, b add _TEMP1, _TEMP2 mov x, _TEMP1 Instead of mov x, a add x, b It'd still be semantically correct, but would it make sense? In the same way, I don't see why the compiler should introduce a needless bit-copy of a (possibly large) structure which can *always* be optimized out when it would be much simpler to skip the bit-copy in the first place. It's an optimization which can always be performed, and so should not be an optimization. It should be the default behavior.
Dec 12 2006
parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Jarrett Billingsley wrote:
<snip>
 Yes, I guess that's true.  But if a simple addition i.e.
 
 x = a + b;
 
 Compiled to
 
 mov _TEMP1, a
 mov _TEMP2, b
 add _TEMP1, _TEMP2
 mov x, _TEMP1
 
 Instead of
 
 mov x, a
 add x, b
 
 It'd still be semantically correct, but would it make sense?
Almost. I could see it generate the following code (with optimizations turned off): mov _TEMP, a add _TEMP, b mov x, _TEMP where a, b and x are memory locations and _TEMP is a register. On x86, you can't add the contents of two memory locations, at least one of the values needs to be in a register. With optimizations on, of course, the variables might be stored in registers instead of memory. If so, your proposed code would be legal.
Dec 12 2006
prev sibling next sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Pragma wrote:
 That's better, but look at what's really happening here.  Inlining and 
 compiler optimization aside, the 'constructor' here creates a Foo on the 
 stack which is then returned and *copied* to the destination 'f'.
That problem was solved 15 years ago (for C and C++). I admit that due to sloth I had failed to implement the solution yet in dmd, but it is implemented now and will go out in the next update. There will be NO extra copies. For whatever the merits of opCall vs this(), efficiency is NOT a problem with either, and is not a reason to choose one or the other. The generated code is the same. Under the hood, they are the same. Both take a hidden pointer to where the result is stored. The rest is window dressing.
Dec 12 2006
next sibling parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Walter Bright wrote:
 Under the hood, they are the same. Both take a hidden pointer to where 
 the result is stored. The rest is window dressing.
That means you can't return the result in a register :o). Andrei
Dec 12 2006
next sibling parent Sean Kelly <sean f4.ca> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Walter Bright wrote:
 Under the hood, they are the same. Both take a hidden pointer to where 
 the result is stored. The rest is window dressing.
That means you can't return the result in a register :o).
That talk can stay on comp.lang.c++.moderated, thank you very much ;-) Sean
Dec 12 2006
prev sibling parent Walter Bright <newshound digitalmars.com> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Walter Bright wrote:
 Under the hood, they are the same. Both take a hidden pointer to where 
 the result is stored. The rest is window dressing.
That means you can't return the result in a register :o).
Actually, it does return it in a register if it fits in one. Try it!
Dec 13 2006
prev sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Walter Bright" <newshound digitalmars.com> wrote in message 
news:elnv14$r2d$1 digitaldaemon.com...

 For whatever the merits of opCall vs this(), efficiency is NOT a problem 
 with either, and is not a reason to choose one or the other. The generated 
 code is the same.

 Under the hood, they are the same. Both take a hidden pointer to where the 
 result is stored. The rest is window dressing.
Alright then. All I've got then is the orthogonality argument. But you won't listen to that either. structs?" and we'll say "you have to use static opCall." And they'll ask "why?" And all we'll be able to do is shake our heads, sigh, and say "I don't know." For the last time, even though you don't care: we have been using static opCall as a WORKAROUND. As in *we never intended for that to be the canonical method of constructing a struct*. I don't think anyone would complain if they were given a consistent, logical method of initializing any aggregate type.
Dec 13 2006
next sibling parent reply "Chris Miller" <chris dprogramming.com> writes:
On Wed, 13 Dec 2006 12:02:37 -0500, Jarrett Billingsley  
<kb3ctd2 yahoo.com> wrote:

 "Walter Bright" <newshound digitalmars.com> wrote in message
 news:elnv14$r2d$1 digitaldaemon.com...

 For whatever the merits of opCall vs this(), efficiency is NOT a problem
 with either, and is not a reason to choose one or the other. The  
 generated
 code is the same.

 Under the hood, they are the same. Both take a hidden pointer to where  
 the
 result is stored. The rest is window dressing.
Alright then. All I've got then is the orthogonality argument. But you won't listen to that either. structs?" and we'll say "you have to use static opCall." And they'll ask "why?" And all we'll be able to do is shake our heads, sigh, and say "I don't know." For the last time, even though you don't care: we have been using static opCall as a WORKAROUND. As in *we never intended for that to be the canonical method of constructing a struct*. I don't think anyone would complain if they were given a consistent, logical method of initializing any aggregate type.
For the record, I'm fine with static opCall. Structs don't have constructors and destructors, but the language just so happens to allow us to have something that looks like a constructor. We could use Foo.create() to initialize new Foo`s but since opCall is available and looks nice to us, we can go with it. If opCall didn't exist, would we even be having this argument? We would know structs weren't meant to have true constructors and destructors. We have an aesthetically pleasing way to initialize structs, so what's the problem. If your friend asks if structs have constructors, you say no, but since the langauge is so expressive, you have this feature called opCall that makes it just as nice to initialize. P.S. I actually don't want a defensive response to this, I'm just stating my view; thanks.
Dec 13 2006
parent reply Mark Wrenn <mark binarytheory.com> writes:
On Wed, 13 Dec 2006 12:21:37 -0500, Chris Miller wrote:

"We know structs weren't meant to have true constructors and destructors.
We have an aesthetically pleasing way to initialize structs, so what's the
problem. If your friend asks if structs have constructors, you say no, but
since the langauge is so expressive, you have this feature called opCall
that makes it just as nice to initialize."

*Amen*
Dec 13 2006
parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Mark Wrenn wrote:
 On Wed, 13 Dec 2006 12:21:37 -0500, Chris Miller wrote:
 
 "We know structs weren't meant to have true constructors and destructors.
 We have an aesthetically pleasing way to initialize structs, so what's the
 problem. If your friend asks if structs have constructors, you say no, but
 since the langauge is so expressive, you have this feature called opCall
 that makes it just as nice to initialize."
 
 *Amen*
If structs do add destructors, this can will be reopened - and by that time it will be much more smellier. Andrei
Dec 13 2006
prev sibling next sibling parent "Lionello Lunesu" <lionello lunesu.remove.com> writes:
Let's not forget that the members of a struct can be initialized "inline":

struct X {
  int a = 2;
}

This makes the lack of constructors for structs a lot less significant.

Additionally, destructors for structs are primarily used for wrappers, RAII, 
and let's not forget scope(...), which is much better suited for RAII than 
destructors ever were. It makes the lack of destructors for structs a lot 
less significant..

So, if people want a C++-looking initialize function, they can use static 
opCall, and it'll look like a call to a constructor (ie. having the same 
name as the struct).

L. 
Dec 13 2006
prev sibling parent Leopold Walkling <leopold_walkling web.de> writes:
Jarrett Billingsley schrieb:
 "Walter Bright" <newshound digitalmars.com> wrote in message 
 news:elnv14$r2d$1 digitaldaemon.com...
 
 For whatever the merits of opCall vs this(), efficiency is NOT a problem 
 with either, and is not a reason to choose one or the other. The generated 
 code is the same.

 Under the hood, they are the same. Both take a hidden pointer to where the 
 result is stored. The rest is window dressing.
Alright then. All I've got then is the orthogonality argument. But you won't listen to that either. structs?" and we'll say "you have to use static opCall." And they'll ask "why?" And all we'll be able to do is shake our heads, sigh, and say "I don't know." For the last time, even though you don't care: we have been using static opCall as a WORKAROUND. As in *we never intended for that to be the canonical method of constructing a struct*. I don't think anyone would complain if they were given a consistent, logical method of initializing any aggregate type.
To me another big problem is the cast to a struct. How could anyone, who is new to D/ didn't study the spec enough expect, that a cast to a struct is done by calling the "opCall". One of those who came from C++ (like me too) would expect the static opCall as the equivalent to those rather rarely used C++-operator()() overloads. What could one answer if some of them ask why it's like this? Couldn't there just be another name than static opCall? Then, to keep it from being ambigous, there could be a change of the let's say opCreate syntax. It doesn't has to be like in C++, I imagine something in the direction of static struct initializers: struct A { int i, j; } int v = 9; A a = A:{a:10, j:v /*not only constants*/};
Dec 14 2006
prev sibling parent Burton Radons <burton-radons smocky.com> writes:
Pragma wrote:
 Stewart Gordon wrote:
 Pragma wrote:
 <snip>
 But I'd like to echo the other comments in this thread regarding 
 structs.  IMO, we're not there yet.  I think folks are looking for a 
 solution that does this:

 - A ctor like syntax for creating a new struct
 - No more forced copy of the entire struct on creation
What do you mean by this?
I'm glad you asked. :) Static opCall() is not a ctor. It never was. People have been clamoring to be able to use this() inside of a struct, much like they can with classes and modules. But the desire here goes beyond mere symmetry between type definitions. The forced copy issue is something that is an artifact of emulating a constructor for a struct. Take the standard approach for example: struct Foo{ int a,b,c; } Foo f = {a:1, b:2, c:3}; Foo f = {1,2,3}; // more succinct version So here we create a struct in place, and break encapsulation in the process. What we really want is an opaque type, that has a little more smarts on creation. Taking advantage of in/body/out would be nice too. No problem, we'll just use opCall(): struct Foo{ int a,b,c; static Foo opCall(int a,int b,int c){ Foo _this; _this.a = a; _this.b = b; _this.c = c; return _this; } } Foo f = Foo(1,2,3); That's better, but look at what's really happening here. Inlining and compiler optimization aside, the 'constructor' here creates a Foo on the stack which is then returned and *copied* to the destination 'f'.
If that's not compiled into a direct write (even to the point of keeping the value virtual unless if it's actually needed in contiguous memory) then there's something wrong with the optimiser.
 To most, that won't ever seem like a problem. But for folks who are 
 working with Vector types or Matrix implementations, that's something to 
 scream about.  In a nutshell, any struct wider than a register that is 
 populated in the 100's to 1000's is wasting cycles needlessly.
I've never liked doing that - if you're going to have very large vectors or matrices, it's usually better just to switch to a programmatic model (run-time sized) rather than keeping it parametric (templated). At some point you're spending more time compiling than you are executing. This requires duplication of a lot of code, which is unacceptable. I've been thinking about this problem for a long time and I still have no solution to it (mixins are so very not the right way); perhaps we're misconsidering how parametric and programmatic types should interact. Let's say that the values used in the constructor of a type are recorded. So we might have (excuse the language): #using: #moki.(size, static array); Matrix := #class { data : static array of (type, rows, cols); // No content needed. #this (type, rows : size, cols : size); }; Now "rows" and "cols" are both automatically constant properties of any created Matrix. If our algorithm requires certain limitations on the matrix, we can "specialise" it: transpose (matrix : Matrix (type, rows, rows)) : Matrix (type, rows, rows) ... And the compiler can generally optimise the Matrix like it were parametric - it could even apply discretionary optimisation so that it doesn't waste compile time on what doesn't even matter - but you could still use it programmatically.
 So that brings us to something like this:
 
 struct Foo{
   int a,b,c;
   this(int a,int b,int c){
     this.a = a;
     this.b = b;
     this.c = c;
   }
 }
 
 Foo f = Foo(1,2,3);
 
 Ambiguity aside, this fixes encapsulation, gives a familiar syntax, and 
 almost fixes the allocation issues.  (see below)
There's still an implied copy, but again, that shouldn't be relevant for any properly-working optimiser.
 - Something that is disambiguated from static opCall
<snip> Do you mean that constructors for structs should have a notation distinct from S(...)?
Well, I think it's one of the reasons why we don't have ctors for structs right now. The preferred syntax for a "struct ctor" would probably be this: S foo = S(a,b,c); Which is indistinct from "static opCall". Throwing 'new' in there wouldn't work either, since that would be a dynamic allocation: S* foo = new S(a,b,c);
"new <struct>" didn't used to parse, and I argued then that it shouldn't because everywhere else the "new" operator exactly described the type it would create. If this special case were removed, it could work as a constructor call. But really I think static opCall should just be killed off.
 So that leaves us with "something else" that provides both a way to 
 invoke a ctor, yet allocates the data on the stack and doesn't force you 
 to create an additional copy:
 
 S foo(a,b,c);  // c++ style
 S foo = stackalloc S(a,b,c); // alloca() style (in place of new)
 S foo = new(stack) S(a,b,c): // another idea
 
Dec 21 2006
prev sibling next sibling parent Alexander Panek <a.panek brainsware.org> writes:
http://digitalmars.com/d/attribute.html#align

--
AlignAttribute:
	align
	align ) Integer )
--

I guess this was meant to be:

--
AlignAttribute:
	align
	align ( Integer )
--


Apart from that .. I just found several new things in the documentation, 
scary. :P
Dec 12 2006
prev sibling parent reply Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
Walter Bright wrote:
 More ABI changes, and implicit [] => * no longer allowed.
 
 http://www.digitalmars.com/d/changelog.html
 
 http://ftp.digitalmars.com/dmd.175.zip
Looks like casts from void* to struct* is broken. Russ
Dec 15 2006
parent Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
Russ Lewis wrote:
 Walter Bright wrote:
 
 More ABI changes, and implicit [] => * no longer allowed.

 http://www.digitalmars.com/d/changelog.html

 http://ftp.digitalmars.com/dmd.175.zip
Looks like casts from void* to struct* is broken. Russ
Not sure what I did wrong or what I'm doing right now, but they seem to be working. Sorry for any confusion I caused.
Dec 21 2006