www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - C++ reference type

reply Tesuji <Tesuji_member pathlink.com> writes:
Will C++ reference type ever be included in D? In one form or the other?
Jun 24 2006
parent reply Yossarian <phoenix flareware.cz> writes:
Tesuji napsal(a):
 Will C++ reference type ever be included in D? In one form or the other?
 
 

reference, and if you want the same behaviour as in c++ references, try inout keyword in declaration: D: void func(inout int a); is equal to C++: void func(int &a);
Jun 24 2006
parent reply Hasan Aljudy <hasan.aljudy gmail.com> writes:
Yossarian wrote:
 Tesuji napsal(a):
 
 Will C++ reference type ever be included in D? In one form or the other?

reference, and if you want the same behaviour as in c++ references, try inout keyword in declaration: D: void func(inout int a); is equal to C++: void func(int &a);

The only thing missing is a reference return type.
Jun 24 2006
next sibling parent reply David Medlock <noone nowhere.com> writes:
Hasan Aljudy wrote:
 Yossarian wrote:
 
 Tesuji napsal(a):

 Will C++ reference type ever be included in D? In one form or the other?

reference, and if you want the same behaviour as in c++ references, try inout keyword in declaration: D: void func(inout int a); is equal to C++: void func(int &a);

The only thing missing is a reference return type.

The attribute functions in classes serve this purpose do they not? C++: int& my_attr(); D: void my_attr( int ); -DavidM use, in both cases: foo.my_attr = 100; -DavidM
Jun 24 2006
parent reply Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
David Medlock wrote:
 The attribute functions in classes serve this purpose do they not?
 
 C++:
 int& my_attr();
 
 D:
 void my_attr( int );
 
 use, in both cases:
 foo.my_attr = 100;

Unless you want to be able to say 'foo.my_attr++;' or 'foo.someStruct.bar = 1;' The latter can be accomplished by making the function return a pointer to the struct, but then you cant say 'Foo x = my_attr();' -- Tomasz Stachowiak /+ a.k.a. h3r3tic +/
Jun 24 2006
parent reply David Medlock <noone nowhere.com> writes:
Tom S wrote:
 David Medlock wrote:
 
 The attribute functions in classes serve this purpose do they not?

 C++:
 int& my_attr();

 D:
 void my_attr( int );

 use, in both cases:
 foo.my_attr = 100;

Unless you want to be able to say 'foo.my_attr++;' or 'foo.someStruct.bar = 1;' The latter can be accomplished by making the function return a pointer to the struct, but then you cant say 'Foo x = my_attr();'

If you want reference semantics use a class. If you want opPostInc use a struct. Even without you could just as easily say: int bar(int r) { return this.someStruct.bar = r; } if you have several 'bars' perhaps its time to refactor? Its like saying : I cant ride my car on bike trails, even though I can ride my bike on roads. Just use the bike. -DavidM
Jun 24 2006
parent reply Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
David Medlock wrote:
 Tom S wrote:
 David Medlock wrote:

 The attribute functions in classes serve this purpose do they not?

 C++:
 int& my_attr();

 D:
 void my_attr( int );

 use, in both cases:
 foo.my_attr = 100;

Unless you want to be able to say 'foo.my_attr++;' or 'foo.someStruct.bar = 1;' The latter can be accomplished by making the function return a pointer to the struct, but then you cant say 'Foo x = my_attr();'

If you want reference semantics use a class. If you want opPostInc use a struct. Even without you could just as easily say: int bar(int r) { return this.someStruct.bar = r; } if you have several 'bars' perhaps its time to refactor? Its like saying : I cant ride my car on bike trails, even though I can ride my bike on roads. Just use the bike.

Excuse me, but I have no idea what you're talking about. Reference return types are useful in many cases. E.g. if you want to implement your own Array class. With normal arrays you can say: int[] arr1; ... ; arr1[0] += 2; but when you define your own array, like: Array!(int) arr2; ... ; then how do you implement 'arr2[0] += 2;' ? You can't, because there are only opIndex and opIndexAssign operators. There's no opIndexAddAssign and opIndex*Assign for that purpose :0 This gets worse, e.g. if you have a struct Foo { int bar; } Foo[] arr3; ... ; arr3[0].bar = 1; but when you define Array!(Foo) arr4; ... ; then to accomplish the 'arr4[0].bar = 1;' functionality, you have to make opIndex return a pointer. Yet then you can't say 'Foo x = arr4[0];' because there's a type mismatch. You'd have to dereference the pointer. -- Tomasz Stachowiak /+ a.k.a. h3r3tic +/
Jun 24 2006
next sibling parent "Andrei Khropov" <andkhropov nospam_mtu-net.ru> writes:
Tom S wrote:

 David Medlock wrote:
 Tom S wrote:
 David Medlock wrote:
 
 The attribute functions in classes serve this purpose do they not?
 
 C++:
 int& my_attr();
 
 D:
 void my_attr( int );
 
 use, in both cases:
 foo.my_attr = 100;

Unless you want to be able to say 'foo.my_attr++;' or 'foo.someStruct.bar = 1;' The latter can be accomplished by making the function return a pointer to the struct, but then you cant say 'Foo x = my_attr();'

If you want reference semantics use a class. If you want opPostInc use a struct. Even without you could just as easily say: int bar(int r) { return this.someStruct.bar = r; } if you have several 'bars' perhaps its time to refactor? Its like saying : I cant ride my car on bike trails, even though I can ride my bike on roads. Just use the bike.

Excuse me, but I have no idea what you're talking about. Reference return types are useful in many cases. E.g. if you want to implement your own Array class. With normal arrays you can say: int[] arr1; ... ; arr1[0] += 2; but when you define your own array, like: Array!(int) arr2; ... ; then how do you implement 'arr2[0] += 2;' ? You can't, because there are only opIndex and opIndexAssign operators. There's no opIndexAddAssign and opIndex*Assign for that purpose :0 This gets worse, e.g. if you have a struct Foo { int bar; } Foo[] arr3; ... ; arr3[0].bar = 1; but when you define Array!(Foo) arr4; ... ; then to accomplish the 'arr4[0].bar = 1;' functionality, you have to make opIndex return a pointer. Yet then you can't say 'Foo x = arr4[0];' because there's a type mismatch. You'd have to dereference the pointer.

Yeah, I agree, D constraints in these cases are really irritating. -- AKhropov
Jun 24 2006
prev sibling parent reply David Medlock <noone nowhere.com> writes:
Tom S wrote:

 David Medlock wrote:
 
 Tom S wrote:

 David Medlock wrote:

 The attribute functions in classes serve this purpose do they not?

 C++:
 int& my_attr();

 D:
 void my_attr( int );

 use, in both cases:
 foo.my_attr = 100;

Unless you want to be able to say 'foo.my_attr++;' or 'foo.someStruct.bar = 1;' The latter can be accomplished by making the function return a pointer to the struct, but then you cant say 'Foo x = my_attr();'

If you want reference semantics use a class. If you want opPostInc use a struct.


This should have read 'value semantics' not opPostInc. <snip>
 Excuse me, but I have no idea what you're talking about. 

I am talking about 'possible' versus 'thats how we do it in C++'. If you want to use C++ idioms, then yes its very hard to use D that way. The point was that its (somewhat of) a shortcoming. Certain C++ idioms require new ones in D. Thats not 'worse', its just different.
 Reference 
 return types are useful in many cases.
 E.g. if you want to implement
 your own Array class. With normal arrays you can say:
 
 int[] arr1; ... ; arr1[0] += 2;
 
 but when you define your own array, like:
 
 Array!(int) arr2; ... ;
 
 then how do you implement 'arr2[0] += 2;' ?
 
 You can't, because there are only opIndex and opIndexAssign operators. 
 There's no opIndexAddAssign and opIndex*Assign for that purpose :0

You can't opAddAssign in that case yes, but you can add 2 to the first item with: arr2[0] = arr2[0] + 2; Is this really a lot worse? Its maybe 2-3 cycles at the most?
 This gets worse, e.g. if you have a
 
 struct Foo {
     int bar;
 }
 
 Foo[] arr3; ... ; arr3[0].bar = 1;
 
 but when you define
 
 Array!(Foo) arr4; ... ;
 
 then to accomplish the 'arr4[0].bar = 1;' functionality, you have to 
 make opIndex return a pointer. Yet then you can't say 'Foo x = arr4[0];' 
 because there's a type mismatch. You'd have to dereference the pointer.

Not to pick but you could say: auto x = arr4[0]; x.bar = 100; or you could simply use a different method: class Array(T) { T[] items; ... T* ptr(int n=0) { return items.ptr + n ; } } arr.ptr[5].bar = 1; Since the built int arrays use a .ptr property, I usually define this method anyways. Once you use it for a while its second nature. If you need to change a lot of entries, use foreach which returns an inout reference. If you want to get crazy: void opIndexAssign( void delegate(T* item) fn, int n ) { fn( items.ptr + n ); } then: arr[0] = (Foo* f){f.bar += 3;}; hehe :P Seriously though, if the item is small enough to not warrant a class, then just extend the container class and define a custom method to update the item. -DavidM
Jun 24 2006
next sibling parent Tesuji <Tesuji_member pathlink.com> writes:
Sounds like you have quite a lot of experience working around the lack of C++
reference type. I just wonder if it will ever be included in D?

Not to pick but you could say:

auto x = arr4[0];
x.bar = 100;

or you could simply use a different method:

class Array(T)
{
   T[]  items;
...
   T*  ptr(int n=0) { return items.ptr + n ; }
}

arr.ptr[5].bar = 1;

Since the built int arrays use a .ptr property, I usually define this 
method anyways.  Once you use it for a while its second nature.

If you need to change a lot of entries, use foreach which returns an 
inout reference.

If you want to get crazy:

   void opIndexAssign( void delegate(T* item) fn, int n )
   {
     fn( items.ptr + n );
   }


then:
arr[0] = (Foo* f){f.bar += 3;};

hehe :P

Seriously though, if the item is small enough to not warrant a class, 
then just extend the container class and define a custom method to 
update the item.

-DavidM

Jun 25 2006
prev sibling parent reply Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
David Medlock wrote:
 Tom S wrote:
 David Medlock wrote:
 If you want reference semantics use a class.
 If you want opPostInc use a struct.


This should have read 'value semantics' not opPostInc.

Heh... now it makes more sense ;)
 <snip>
 Excuse me, but I have no idea what you're talking about. 

I am talking about 'possible' versus 'thats how we do it in C++'. If you want to use C++ idioms, then yes its very hard to use D that way. The point was that its (somewhat of) a shortcoming. Certain C++ idioms require new ones in D. Thats not 'worse', its just different.

If it requires you to create workarounds all over the place, makes code less transparent than the C++ way and is less efficient, then I'm tempted to say that it's worse.
   You can't opAddAssign in that case yes, but you can add 2 to the first 
 item with:
 
   arr2[0] = arr2[0] + 2;
 
 Is this really a lot worse?  Its maybe 2-3 cycles at the most?

It's not about speed in *this* case. The compiler might optimize it away. The point is that if you use normal arrays in some place, but then decide to switch to your custom classes, you may be in trouble. Say you have some myArray which stores structs and somewhere you write: myArray[i].prop = 5; The code previously used a builtin array and was just fine. After changing to the custom array template, it doesnt work as expected. It changes the 'prop' in a temporary object. Ok, you then realize what is up and modify the opIndex to return a pointer. But then all of a sudden code like this: MyStruct foo = myArray[i]; stops working because myArray[i] returns a pointer and there's a mismatch. Ok, you fix all of these lines to MyStruct foo = *myArray[i]; You live happily, until one day you find out that your custom logging functions are behaving in a weird way. Instead of printing the contents of your MyStruct's, they output some ridiculous hexadecimal numbers. You jump into the code just to find out (after some struggles), that the wrong logging function was called, because you have void log(MyStruct); and void log(void*); and somewhere in your code you had log(myArray[i]). No error was reported because the compiler silently assumed that you wanted to print a pointer.
 This gets worse, e.g. if you have a

 struct Foo {
     int bar;
 }

 Foo[] arr3; ... ; arr3[0].bar = 1;

 but when you define

 Array!(Foo) arr4; ... ;

 then to accomplish the 'arr4[0].bar = 1;' functionality, you have to 
 make opIndex return a pointer. Yet then you can't say 'Foo x = 
 arr4[0];' because there's a type mismatch. You'd have to dereference 
 the pointer.

Not to pick but you could say: auto x = arr4[0]; x.bar = 100;

You could with classes, but in this case typeof(arr4[0]) is a struct.
 or you could simply use a different method:
 
 class Array(T)
 {
   T[]  items;
 ...
   T*  ptr(int n=0) { return items.ptr + n ; }
 }
 
 arr.ptr[5].bar = 1;
 
 Since the built int arrays use a .ptr property, I usually define this 
 method anyways.  Once you use it for a while its second nature.

Yet I don't feel like losing array bounds checking... It would have to be 'arr.ptr(5).bar = 1;' plus a modification to the 'ptr' function. But then it doesn't read as 'arr[5].bar = 1;'. At the moment the language doesn't provide enough abstraction in this area. At least IMO.
 If you need to change a lot of entries, use foreach which returns an 
 inout reference.

Sure.
 If you want to get crazy:
 
   void opIndexAssign( void delegate(T* item) fn, int n )
   {
     fn( items.ptr + n );
   }
 
 
 then:
 arr[0] = (Foo* f){f.bar += 3;};
 
 hehe :P

Ummm... no, thanks :P
 Seriously though, if the item is small enough to not warrant a class, 
 then just extend the container class and define a custom method to 
 update the item.

That's a corner case which misses the point a bit... -- Tomasz Stachowiak /+ a.k.a. h3r3tic +/
Jun 25 2006
parent reply David Medlock <ashleymedlock no.spam.yahoo.com> writes:
Tom S wrote:
 David Medlock wrote:
 
 If it requires you to create workarounds all over the place, makes code 
 less transparent than the C++ way and is less efficient, then I'm 
 tempted to say that it's worse.
 
 
 
   You can't opAddAssign in that case yes, but you can add 2 to the 
 first item with:

   arr2[0] = arr2[0] + 2;

 Is this really a lot worse?  Its maybe 2-3 cycles at the most?

It's not about speed in *this* case. The compiler might optimize it away. The point is that if you use normal arrays in some place, but then decide to switch to your custom classes, you may be in trouble. Say you have some myArray which stores structs and somewhere you write: myArray[i].prop = 5;

Agreed, changing to a custom class would bite you there.
 You live happily, until one day you find out that your custom logging 
 functions are behaving in a weird way. Instead of printing the contents 
 of your MyStruct's, they output some ridiculous hexadecimal numbers. You 
 jump into the code just to find out (after some struggles), that the 
 wrong logging function was called, because you have
 
 void log(MyStruct);
 and
 void log(void*);
 
 and somewhere in your code you had log(myArray[i]). No error was 
 reported because the compiler silently assumed that you wanted to print 
 a pointer.
 
 
 
 This gets worse, e.g. if you have a

 struct Foo {
     int bar;
 }

 Foo[] arr3; ... ; arr3[0].bar = 1;

 but when you define

 Array!(Foo) arr4; ... ;

 then to accomplish the 'arr4[0].bar = 1;' functionality, you have to 
 make opIndex return a pointer. Yet then you can't say 'Foo x = 
 arr4[0];' because there's a type mismatch. You'd have to dereference 
 the pointer.

Not to pick but you could say: auto x = arr4[0]; x.bar = 100;

You could with classes, but in this case typeof(arr4[0]) is a struct.

I meant in the case you returned a pointer, but yes that wouldn't follow the 'native' array semantics so it wouldn't be good to return a pointer. Maybe with the static if you could make your container behave different with non-object types?
 
 
 or you could simply use a different method:

 class Array(T)
 {
   T[]  items;
 ...
   T*  ptr(int n=0) { return items.ptr + n ; }
 }

 arr.ptr[5].bar = 1;

 Since the built int arrays use a .ptr property, I usually define this 
 method anyways.  Once you use it for a while its second nature.

Yet I don't feel like losing array bounds checking... It would have to be 'arr.ptr(5).bar = 1;' plus a modification to the 'ptr' function. But then it doesn't read as 'arr[5].bar = 1;'. At the moment the language doesn't provide enough abstraction in this area. At least IMO.

Agreed. There is a lack there. It just hasn't bit me a lot thus far (and I have written a fair amount of code in D). I don't see a clear solution short of reference/inout returns, but I wonder what that would do to the rest of the language semantics. I can't recall if Walter said why he didn't include them.
 
 If you want to get crazy:

   void opIndexAssign( void delegate(T* item) fn, int n )
   {
     fn( items.ptr + n );
   }


 then:
 arr[0] = (Foo* f){f.bar += 3;};

 hehe :P

Ummm... no, thanks :P

Tongue in cheek example of course, but I'm so far liking the delegate syntax.
 
 
 Seriously though, if the item is small enough to not warrant a class, 
 then just extend the container class and define a custom method to 
 update the item.

That's a corner case which misses the point a bit...

updating a single member of a struct. In that case hiding the use of a pointer would be IMO ok. We aren't really disagreeing on any implementation details, only on their impact on daily use. Cheers. -DavidM
Jun 25 2006
parent Tom S <h3r3tic remove.mat.uni.torun.pl> writes:
David Medlock wrote:
 Tom S wrote:
 You could with classes, but in this case typeof(arr4[0]) is a struct.

I meant in the case you returned a pointer,

Ah.. sorry then ;)
 but yes that wouldn't follow 
 the 'native' array semantics so it wouldn't be good to return a pointer. 
  Maybe with the static if you could make your container behave different 
 with non-object types?

That's the approach I've proposed some time ago to someone implementing a custom container class. Not very consistent, but if there's nothing better at the moment...
 It just hasn't bit me a lot thus far (and I have written a fair amount of code
in D)
 I don't see a clear solution short of reference/inout returns, but I wonder
what
 that would do to the rest of the language semantics.

I've had a few occasions to miss this feature from C++ ... I'm not sure if reference return types would be very hard to implement in the compiler, but a good syntax would have to be developed and probably the 'inout' keyword wouldn't cut it, e.g. void foo (inout int function()); Does foo take an inout function pointer or does it take a function that returns an int by reference ? Putting 'inout' after the basic type could work, e.g. void foo(int inout function()); but then it's inconsistent with void bar(inout int x); If it was supposed to be consistent in this case, that 'bar' decl would have to become 'void bar(int inout x)'... but it doesn't read fine. But then renaming inout to 'ref' would make it sound ok again, e.g. void bar(int ref x) void foo(int ref function()); but then I wouldn't see a point in not using the '&' token instead. What would it do to other semantics ? Not much, I guess, but people would want references everywhere and not only for return and parameter types :P
 Ummm... no, thanks :P

Tongue in cheek example of course, but I'm so far liking the delegate syntax.

It's not *very* bad, but I've got no idea what performance consequences it yields.
 We aren't really disagreeing on any implementation details, only on 
 their impact on daily use.

True. I don't want it to done like in C++, but at least *something* ought to be done. E.g. to make various custom containers seem more like built-ins. -- Tomasz Stachowiak /+ a.k.a. h3r3tic +/
Jun 25 2006
prev sibling parent reply Dave <Dave_member pathlink.com> writes:
Hasan Aljudy wrote:
 Yossarian wrote:
 Tesuji napsal(a):

 Will C++ reference type ever be included in D? In one form or the other?

reference, and if you want the same behaviour as in c++ references, try inout keyword in declaration: D: void func(inout int a); is equal to C++: void func(int &a);

The only thing missing is a reference return type.

There's one more irritating limitation, and that is the ability to pass a temporary byref: private import std.stdio; void main() { // error: (opCall)(100) is not an lvalue writefln(foo(MyStruct(100),100)); } int foo(inout MyStruct s, int i) { return s.i * i; } struct MyStruct { private int i = 0; static MyStruct opCall(int i) { MyStruct s; s.i = i; return s; } } I think the 'byref' modifier could be added to allow this. In the compiler it could be implemented the same as 'inout' except that the lvalue limitation would not be enforced. In C++ you'd have to do 'const <type> &', but I'm not suggesting we get into the const debate again just for this purpose; just add the byref for cases like that, for cases where the programmer just wants to pass byref w/o an intention to modify the parameter, and also the 'byref' modifier could then be used for function return types as well (makes more sense to me than using 'inout' for return types). - Dave
Jun 24 2006
next sibling parent cschueler <cschueler_member pathlink.com> writes:
An L-value return is needed to make any custom type truly transparent with a
builtin type. This holds for custom containers as well as custom number types.
Consider:

int a;
(+a) = 3

Now change the type of a into MyVector or MyMatrix, and it wouldn't work.
Operator + returns an L-value. As of current D, you couldn't write an operator +
that returns an L-value. 

The charm of C/C++ is that there is no magic exclusive to the compiler. In
Pascal for instance, the WriteLn function can take a variable parameter list of
serveral types (string, int) and print it out. There is no way to write a
comparable replacement function for WriteLn standard Pascal itself, it's a
compiler magic. Java has a GC, but you cannot write a GC in Java; it must be
done for you by superior powers.

It would be a pity if D retains compiler magic in the form of privileged builtin
types, because there is no way to return an L-value of a value type for custom
code.










In article <e7ju5f$18lf$1 digitaldaemon.com>, Dave says...
Hasan Aljudy wrote:
 Yossarian wrote:
 Tesuji napsal(a):

 Will C++ reference type ever be included in D? In one form or the other?

reference, and if you want the same behaviour as in c++ references, try inout keyword in declaration: D: void func(inout int a); is equal to C++: void func(int &a);

The only thing missing is a reference return type.

There's one more irritating limitation, and that is the ability to pass a temporary byref: private import std.stdio; void main() { // error: (opCall)(100) is not an lvalue writefln(foo(MyStruct(100),100)); } int foo(inout MyStruct s, int i) { return s.i * i; } struct MyStruct { private int i = 0; static MyStruct opCall(int i) { MyStruct s; s.i = i; return s; } } I think the 'byref' modifier could be added to allow this. In the compiler it could be implemented the same as 'inout' except that the lvalue limitation would not be enforced. In C++ you'd have to do 'const <type> &', but I'm not suggesting we get into the const debate again just for this purpose; just add the byref for cases like that, for cases where the programmer just wants to pass byref w/o an intention to modify the parameter, and also the 'byref' modifier could then be used for function return types as well (makes more sense to me than using 'inout' for return types). - Dave

Jun 26 2006
prev sibling parent reply BCS <BCS pathlink.com> writes:
How about some sort of cast to l-value syntax that would be used from 
the calling code. It would generate a copy and pass a reference to it.
For that matter, any reason not to do that by default?

Dave wrote:
 

 
 
 There's one more irritating limitation, and that is the ability to pass 
 a temporary byref:
 
 private import std.stdio;
 void main()
 {
     // error: (opCall)(100) is not an lvalue
     writefln(foo(MyStruct(100),100));
 }
 int foo(inout MyStruct s, int i)
 {
     return s.i * i;
 }
 struct MyStruct
 {
     private int i = 0;
     static MyStruct opCall(int i)
     {
         MyStruct s;
         s.i = i;
         return s;
     }
 }
 

Jun 26 2006
parent Dave <Dave_member pathlink.com> writes:
BCS wrote:
 
 How about some sort of cast to l-value syntax that would be used from 
 the calling code. It would generate a copy and pass a reference to it.
 For that matter, any reason not to do that by default?
 

I don't think there is -- that's kinda where I was headed with this. I don't think the 'lvalue' limitation needs to be there because either way a temporary would already be created by the opCall (whether it is passed 'in', 'out' or 'inout'). That is, the temporary is already created anyhow - why not just remove the restriction. Besides the coding convenience, the reason I suggested the new 'byref' modifier is because the current 'lvalue' limitation kind-of makes sense in the context of 'out' or 'inout'. But there are lots of times when you may just want to pass something 'byref' not intending it to be modified (but that side effect _will_ still be there - I'm not suggesting some sort of C++-like const &). - Dave
 Dave wrote:

 There's one more irritating limitation, and that is the ability to 
 pass a temporary byref:

 private import std.stdio;
 void main()
 {
     // error: (opCall)(100) is not an lvalue
     writefln(foo(MyStruct(100),100));
 }
 int foo(inout MyStruct s, int i)
 {
     return s.i * i;
 }
 struct MyStruct
 {
     private int i = 0;
     static MyStruct opCall(int i)
     {
         MyStruct s;
         s.i = i;
         return s;
     }
 }


Jun 26 2006