www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - pointers-to-members, (custom) array implementation

reply Wolfgang Draxinger <wdraxinger darkstargames.de> writes:
I'm currently in a discussion D vs. C++ and in some points I must
admit, that the other side has some point, I must admit, too:

Pointers to members are a feature that D currently lacks, but
sometimes they are quite usefull and can't be replaced by
delegates.

E.g. say you got a class "foo" which has several member functions
of the same prototype and several member variables. Now you want
to write some function that performs some action on the class,
but is not fixed on certain members. That function would accept
the instance of a class, and pointers-to-members as parameters.
The whole thing could be used in a foreach loop.

For the member variables one could use the .offset property to
supply their offset address relative to the class instance
address, but for member functions emulating such functionality
would be quite a hack. I think that pointers-to-members are as
important as delegates.

Another point they claim is, that having the full implementation
for dynamic and associative arrays within the _language itself_
is not clean. Instead the language should define a universal
interface for dynamic array classes.

I'm thinking of the following ways, how to specify a custom array
class implementation:

type[] foo = new!(My_Array) type[];

i.e. new is a template creating a instance of My_Array, calling
that's constructor with the apropriate values (I come to that
later). Normally new defaults to the builtin class Array (just
like Object is a builtin class). I don't have a clear idea yet
how this should interface with class allocators, but one
solution would be, that a custom array class should use that
allocator for memory allocation instead of the array class
default's. But an array class should be allowed to override that
request.

Since pragmatic programmers are lazy programmers it should also
be possible to define a array class for a whole scope. A pragma
that affects the current scope and all subscope would be fine,
e.g.

pragma([], My_Array);

if a custom array class is desired only for specific types

pragma(type[], My_TypeArray);

And of course multiple pragmas should be possible, as long the
override is not ambigous. However within one scope only one
pragma per Type is allowed, but a subscope may override again. 

This is a point where pointers-to-members would come in handy:
Since a resizing of the array possibly requires constructors to
be called the C++ approach on dynamic arrays is templates.
But actually managing an array one requires the size of each
element, stride, length and amount of allocated memory. Only
constructor and destructor calling is a type specific. But here
can pointers-to-members help. For each class a pointer-to-member
to the constructor function is created. A Array class then can
use that pointer to member to call the constructor for newly
allocated classes. Of course only the (eventually not present)
default constructor would be called, after the elements contents
would have been initialized.

A custom Array class constructor must accept the following
parameters then: The beginning length of the array, the total
amount of memory to allocate (this is to allow the compiler to
tell the Array class, that more memory is needed for elements
appended in the following code), a pointer to TypeInfo, a
pointer to ClassInfo; the ClassInfo has the pointer-to-member of
the constructor and the initializer data, the TypeInfo contains
information about size and alignment for one element.

To designate pointer-to-members the following might be
apropriate:

class foo
{
        int bar;
        char*[] spam;
        void eggs(int);
};

foo.*int a; // a is a pointer to a int member of foo
foo.*char*[] b; // b is a pointer to an array of pointers to char
member of foo
foo.*void function(int) c; // c is a pointer to a member function
taking an int

To get a pointer-to-member the following syntax might be usable

a = foo.&bar;
b = foo.&spam;
c = foo.&eggs;

Using the pointers-to-members is consistent with normal pointer
syntax:
Normal pointers are designated by '*' and dereferenced by '*',
too. The address is retrieved by '&'. By prepending the member
operator '.' ".*" can be read right to left (which is the way
types get defined) as "pointer (to) member", and also
as "dereference member". Similairily ".&" can be read as "get
address of member". So dereferencing would be written as:

foo TheFoo = new foo;
TheFoo.*a = 5;
printf(TheFoo.*b[3]);
TheFoo.*c(10);

which is consistent with C++ syntax.

Wolfgang Draxinger
-- 
E-Mail address works, Jabber: hexarith jabber.org, ICQ: 134682867
Feb 07 2007
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Wolfgang Draxinger" <wdraxinger darkstargames.de> wrote in message 
news:uoaq94-f0c.ln1 darkstargames.dnsalias.net...
 I'm currently in a discussion D vs. C++ and in some points I must
 admit, that the other side has some point, I must admit, too:

 Pointers to members are a feature that D currently lacks, but
 sometimes they are quite usefull and can't be replaced by
 delegates.

 E.g. say you got a class "foo" which has several member functions
 of the same prototype and several member variables. Now you want
 to write some function that performs some action on the class,
 but is not fixed on certain members. That function would accept
 the instance of a class, and pointers-to-members as parameters.
 The whole thing could be used in a foreach loop.
It's not quite as elegant as pointers-to-members, but it's something: class A { static A members; static this() { members = new A(); } int fork() { return 1; } int knife() { return 2; } } void func(A a, int delegate()[] members...) { foreach(member; members) { member.ptr = a; writefln(member()); } } void main() { A a = new A(); func(a, &A.members.fork, &A.members.knife); } The ugly parts are (1) having to create an instance of A in order to get the offsets of the member functions (the static 'members' variable) and (2) having to set the delegate .ptr before calling it, rather than just calling something like "a.*member()". I think pointers-to-members have a bad reputation but I agree that they can be really useful in some cases.
Feb 07 2007
parent Wolfgang Draxinger <wdraxinger darkstargames.de> writes:
Jarrett Billingsley wrote:

 It's not quite as elegant as pointers-to-members, but it's
 something:

 [some hack]
I didn't neglegt the possibility of some hack to get the very same behaviour. The most ugly variant is possibly the following (and that works only in some ABI): class Foo { int bar(char[]); }; int function(Foo*, char[]) spam; spam = cast(int function(Foo*, char[])cast(void*)&Foo.bar; void eggs() { Foo foobar = new Foo; spam(&foobar, "spam'n'eggs"); } In some ABIs this actually works, and it't e.g. the way one uses DirectX from languages, that don't have interfaces/classes. But it's ugly and D is all about elegance IMHO. So I think as of one the next versions D should pointer to members. Those don't break anything, the language remains orthogonal (since PtM fill a gap, that one currently has to fill with a hack). There are also other things _I_ miss and that woudn't really hurt, to have in D. For example bitfields. Currently the best way to emulate them is a struct having some static array of required length and properties to get/set the values. Eventually a new stucture type "bitfield" may be introduced with some special rules. E.g. within a bitfield there can be declared no variables by basic types. One can only specify if a certain variable is to be treated signed or unsigned and the amount of bits it consumes. And there should be some way to define padding. Bitfields should align and the gap to the next alignment boundary should be consumed, too. And bitfields cannot stand for themself and must always be embedded into a struct or union. e.g. struct bar { bitfield { unsigned flags: 5; signed offset: 4; void: 5; // pad 5 bits. unsigned count: 15; }; // assume a 4 byte aligning, the bitfield // will consume another 3 bits to fill up }; Since nesting rules apply the members of the annonymous nested bitfield can be accessed like bar.flags = 1 | 2 | 8; bar.offset = -10; bar.count = 10000; There are some libraries, e.g. SDL, that use bitfields extensivly. Having them in the language would be very, very nice. I'm not about featureitis, but elegance. As soon as a language requires me to use some hack, to achieve my goal it doesn't seem elegant to me. Wolfgang Draxinger -- E-Mail address works, Jabber: hexarith jabber.org, ICQ: 134682867
Feb 08 2007
prev sibling parent reply BCS <BCS pathlink.com> writes:
Wolfgang Draxinger wrote:
 I'm currently in a discussion D vs. C++ and in some points I must
 admit, that the other side has some point, I must admit, too:
 
 Pointers to members are a feature that D currently lacks, but
 sometimes they are quite usefull and can't be replaced by
 delegates.
 
 E.g. say you got a class "foo" which has several member functions
 of the same prototype and several member variables. Now you want
 to write some function that performs some action on the class,
 but is not fixed on certain members. That function would accept
 the instance of a class, and pointers-to-members as parameters.
 The whole thing could be used in a foreach loop.
 
 For the member variables one could use the .offset property to
 supply their offset address relative to the class instance
 address, but for member functions emulating such functionality
 would be quite a hack. I think that pointers-to-members are as
 important as delegates.
 
I'd have to check but I think this works class C { int one(int i){...} int two(int i){...} int three(int i){...} } int Pt2Mem!(alias go)(C c, int i) { return c.go(i); } auto fn = &Pt2Mem!(one); C c = new C; c.fn(1); The tail recursion should get optimized away and maybe even the first call in some cases.
Feb 07 2007
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"BCS" <BCS pathlink.com> wrote in message 
news:eqe3gf$159e$4 digitaldaemon.com...

 I'd have to check but I think this works

 class C
 {
 int one(int i){...}
 int two(int i){...}
 int three(int i){...}
 }

 int Pt2Mem!(alias go)(C c, int i)
 {
 return c.go(i);
 }


 auto fn = &Pt2Mem!(one);

 C c = new C;

 c.fn(1);
Nope. Not only can you not use alias parameters in that way (they have to be a declared symbol, not just an arbitrary identifier), but you also can't call "obj.anything()" like "c.fn(1)". I was hoping it might be possible to use mixin() to create an arbitrary identifier, but unfortunately: c.mixin("one")(i); doesn't compile.
Feb 07 2007
parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Jarrett Billingsley wrote:
 I was hoping it might be possible to use mixin() to create an arbitrary 
 identifier, but unfortunately:
 
 c.mixin("one")(i);
 
 doesn't compile. 
Try these (off the top of my head): --- int Pt2Mem!(char[] go)(C c, int i) { return mixin("c." ~ go ~ "(i)"); } --- or: --- int Pt2Mem!(char[] go)(C c, int i) { mixin("return c." ~ go ~ "(i)"); } --- IIRC you can only mixin complete expressions, statements or declarations[1]. Declarations aren't really useful here, but expressions and statements should work... [1]: Well, and templates, but that doesn't work with strings.
Feb 08 2007
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Frits van Bommel" <fvbommel REMwOVExCAPSs.nl> wrote in message 
news:eqess1$2ito$1 digitaldaemon.com...
 Try these (off the top of my head):
 ---
 int Pt2Mem!(char[] go)(C c, int i)
 {
     return mixin("c." ~ go ~ "(i)");
 }
This one works just fine :)
 or:
 ---
 int Pt2Mem!(char[] go)(C c, int i)
 {
     mixin("return c." ~ go ~ "(i)");
 }
 ---
This one has to have a semicolon at the end of the string, since the mixin statement strings have to be complete statements. But this works too.
Feb 08 2007
parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Jarrett Billingsley wrote:
 "Frits van Bommel" <fvbommel REMwOVExCAPSs.nl> wrote in message 
 news:eqess1$2ito$1 digitaldaemon.com...
 Try these (off the top of my head):
[snip first]
 
 This one works just fine :)
 
 or:
[snip second]
 
 This one has to have a semicolon at the end of the string, since the mixin 
 statement strings have to be complete statements.  But this works too. 
You read that first sentence, right? (especially the parenthesized part) That was a warning I didn't actually compile these :P.
Feb 08 2007
parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Frits van Bommel" <fvbommel REMwOVExCAPSs.nl> wrote in message 
news:eqfj04$e65$1 digitaldaemon.com...

 You read that first sentence, right? (especially the parenthesized part)

 That was a warning I didn't actually compile these :P.
You know, I wasn't trying to be a dick.
Feb 08 2007
prev sibling next sibling parent reply Wolfgang Draxinger <wdraxinger darkstargames.de> writes:
BCS wrote:

 I'd have to check but I think this works
 
 class C
 {
 int one(int i){...}
 int two(int i){...}
 int three(int i){...}
 }
 
 int Pt2Mem!(alias go)(C c, int i)
 {
 return c.go(i);
 }
 
 
 auto fn = &Pt2Mem!(one);
 
 C c = new C;
 
 c.fn(1);
Works but is IMHO not very elegant. Make PtM a language feature and it's get a lot more readable. I don't like it. Wolfgang Draxinger -- E-Mail address works, Jabber: hexarith jabber.org, ICQ: 134682867
Feb 08 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Wolfgang Draxinger wrote:
 BCS wrote:
 
 I'd have to check but I think this works

 class C
 {
 int one(int i){...}
 int two(int i){...}
 int three(int i){...}
 }

 int Pt2Mem!(alias go)(C c, int i)
 {
 return c.go(i);
 }


 auto fn = &Pt2Mem!(one);

 C c = new C;

 c.fn(1);
Works but is IMHO not very elegant. Make PtM a language feature and it's get a lot more readable. I don't like it.
No, it does not work. &Pt2Mem!(one) gives a 'what's "one"?' error. And the c.fn part gives a 'no such member "fn"' error. But if it did work I think it would be a pretty reasonably elegant way to do it. :-) --bb
Feb 08 2007
parent Wolfgang Draxinger <wdraxinger darkstargames.de> writes:
Bill Baxter wrote:

 Works but is IMHO not very elegant. Make PtM a language
 feature and it's get a lot more readable. I don't like it.
No, it does not work. &Pt2Mem!(one) gives a 'what's "one"?' error. And the c.fn part gives a 'no such member "fn"' error.
I buy a "If it" before the "Works"... I've hit the submit button a bit too early. Wolfgang Draxinger -- E-Mail address works, Jabber: hexarith jabber.org, ICQ: 134682867
Feb 08 2007
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
BCS wrote:
 
 I'd have to check but I think this works
 
 class C
 {
     int one(int i){...}
     int two(int i){...}
     int three(int i){...}
 }
 
 int Pt2Mem!(alias go)(C c, int i)
 {
     return c.go(i);
 }
 
 
 auto fn = &Pt2Mem!(one);
 
 C c = new C;
 
 c.fn(1);
Huh. I had no idea that free functions could be called this way with a class as the first parameter. I thought it only worked for arrays. Sean
Feb 08 2007
parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Sean Kelly wrote:
 BCS wrote:
 I'd have to check but I think this works

 class C
 {
     int one(int i){...}
     int two(int i){...}
     int three(int i){...}
 }

 int Pt2Mem!(alias go)(C c, int i)
 {
     return c.go(i);
 }


 auto fn = &Pt2Mem!(one);

 C c = new C;

 c.fn(1);
Huh. I had no idea that free functions could be called this way with a class as the first parameter. I thought it only worked for arrays.
Either you're being sarcastic or you didn't even _try_ to compile that...
Feb 08 2007
parent Sean Kelly <sean f4.ca> writes:
Frits van Bommel wrote:
 Sean Kelly wrote:
 BCS wrote:
 I'd have to check but I think this works

 class C
 {
     int one(int i){...}
     int two(int i){...}
     int three(int i){...}
 }

 int Pt2Mem!(alias go)(C c, int i)
 {
     return c.go(i);
 }


 auto fn = &Pt2Mem!(one);

 C c = new C;

 c.fn(1);
Huh. I had no idea that free functions could be called this way with a class as the first parameter. I thought it only worked for arrays.
Either you're being sarcastic or you didn't even _try_ to compile that...
The latter :-) Sean
Feb 08 2007