digitalmars.D.learn - Template class with dispatched properties
- Ross Hays (17/17) Nov 07 2013 I have been playing around with a vector implementation that I am
- Chris Cain (32/37) Nov 07 2013 Greetings,
- Chris Cain (25/27) Nov 07 2013 Actually, I refactored it a little bit to make it better
- Ross Hays (9/9) Nov 07 2013 Awesome that seems to do what I was going for. I had tried a
- Ross Hays (28/28) Nov 07 2013 I am actually a little curious why my original approach did not
- Chris Cain (19/47) Nov 07 2013 Strange. I'm getting a different error, but I'm still running
- Chris Cain (2/3) Nov 07 2013 Also, define `toOffset` in the template constraint.
- Ross Hays (23/41) Nov 07 2013 Okay 2 is fixed, leftovers of other languages in my mind.
- Ross Hays (21/64) Nov 07 2013 Just reread and realized I glossed over your mention of static if.
- Chris Cain (8/27) Nov 07 2013 Sorry, I forgot to mention in that post that you have "toOffset"
- Ross Hays (25/32) Nov 07 2013 And more stupid mistakes on my part. Thank you, still not 100%
- Chris Cain (17/32) Nov 07 2013 No problem. I'm glad we're making progress on it. And don't feel
- Ross Hays (36/36) Nov 07 2013 Boom, that last few were the issues. I elected to just move the
- Ross Hays (8/8) Nov 07 2013 And let me say that I really do like that this works in D. I
- Philippe Sigaud (5/12) Nov 07 2013 Then the next step is to allow swizzling (sp?) :-)
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (5/19) Nov 25 2013 Manu Evans had reported either during his talk at DConf 2013 or during
- Ross Hays (10/10) Nov 07 2013 Okay here is something I was hoping for some general
- Chris Cain (30/40) Nov 07 2013 Yes, D is the same as C++ in that each unique template
- bearophile (4/7) Nov 08 2013 The idiomatic way to do that is to use "enum offset = ...;".
- Chris Cain (13/22) Nov 07 2013 Indeed. Presumably you could modify it to shift the starting
I have been playing around with a vector implementation that I am trying to write in D, and have been having problems getting something to work (it probably isn't even possible). My end goal is to be able to instantiate a vector with a syntax like... `Vector!(2, float) vec = new Vector!(2, float)();` Now this part I can get working by having `class Vector(int i, T)` and an array such as `T[i] data`. The thing that I have been trying to do, is allow access to the components of the vector through properties that are identified by letter. Ideally it would work like `vec.x = 4.0f;` or something like that. I have tried using property and the opDispatch method together, but that does not compile. I know I can just use array indicies to access components, but first I wanted to see if this was possible. Any suggestions? Thank you, Ross Hays
Nov 07 2013
On Friday, 8 November 2013 at 02:13:01 UTC, Ross Hays wrote:My end goal is to be able to instantiate a vector with a syntax like... `Vector!(2, float) vec = new Vector!(2, float)();` ... Any suggestions?Greetings, This works: --- import std.stdio; struct Vector(int N, T) if (N <= 3) { private T[N] data; public property void opDispatch(string fieldName, Args ...)(Args args) if (Args.length == 1 && fieldName.length == 1 && cast(size_t)(fieldName[0] - 'x') < N) { static immutable offset = cast(size_t)(fieldName[0] - 'x'); data[offset] = args[0]; } public property T opDispatch(string fieldName, Args ...)(Args args) if (Args.length == 0 && fieldName.length == 1 && cast(size_t)(fieldName[0] - 'x') < N) { static immutable offset = cast(size_t)(fieldName[0] - 'x'); return data[offset]; } } void main() { auto vec = Vector!(2, float)(); vec.x = 1.0; vec.y = 2.0; //vec.z = 3.0; // Doesn't compile, as expected for a Vector!(2, float); writeln("vec.x = ", vec.x, " and vec.y = ", vec.y); } --- Minor tweaks might be necessary, but that should get you started.
Nov 07 2013
On Friday, 8 November 2013 at 02:48:31 UTC, Chris Cain wrote:Minor tweaks might be necessary, but that should get you started.Actually, I refactored it a little bit to make it better (original code was just a bit too messy for my taste): --- struct Vector(int N, T) if (N <= 3) { private T[N] data; private static size_t toOffset(string fieldName) { return cast(size_t)(fieldName[0] - 'x'); } public property auto opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && toOffset(fieldName) < N) { static immutable offset = toOffset(fieldName); static if(Args.length == 0) { // getter return data[offset]; } else { // setter data[offset] = args[0]; } } } ---
Nov 07 2013
Awesome that seems to do what I was going for. I had tried a similar approach with property dispatch and the subtraction of 'x', but I had left out the static if and had the opDispatch returning a ref of the entry in the array (so there would just be the one property still) but that resulted in "Error: no property 'x' for type 'test.Vector!(2, float).Vector'". This is interesting, though probably not a very safe way to handle vectors in the real world (even more so if they are going to be vectors of more than length 3).
Nov 07 2013
I am actually a little curious why my original approach did not work at all. Using some of what you provided and some of what I had I get the following: import std.stdio; import std.string; class Vector(int N, T) if (N <= 3) { T[N] data; this() { data[] = 0; } property ref T opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && toOffset(fieldName) < N) { int offset = fieldName.charAt(0) - 'x'; return data[offset] = args[0]; } } void main() { Vector!(2, float) t = new Vector!(2,float)(); writeln(t.x); } Which still errors out with: Error: no property 'x' for type 'test.Vector!(2, float).Vector' So that is odd.
Nov 07 2013
On Friday, 8 November 2013 at 03:42:12 UTC, Ross Hays wrote:I am actually a little curious why my original approach did not work at all. Using some of what you provided and some of what I had I get the following: import std.stdio; import std.string; class Vector(int N, T) if (N <= 3) { T[N] data; this() { data[] = 0; } property ref T opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && toOffset(fieldName) < N) { int offset = fieldName.charAt(0) - 'x'; return data[offset] = args[0]; } } void main() { Vector!(2, float) t = new Vector!(2,float)(); writeln(t.x); } Which still errors out with: Error: no property 'x' for type 'test.Vector!(2, float).Vector' So that is odd.Strange. I'm getting a different error, but I'm still running 2.063.2. The error I get is `Error: cannot resolve type for t.opDispatch!("x")` What version are you running? In any case, the reason apparently is multifold: 1. Apparently the proper error message isn't shown when using the property notation. (I'd have to check to see if it happens in 2.064 ... might be a fixed bug) 2. `.charAt(0)` doesn't exist for D's strings. You can just use bracket notation to access the index. 3. When args is empty (as it will be for a getter, when you call) args[0] doesn't exist, so `Error: array index [0] is outside array bounds [0 .. 0]` So fix 2 and 3 and it works for getting x. The reason I use a static if is to separate the cases where args has items and when it does not (since args[0] is invalid when args.length == 0), so that'll be necessary to get it to work.
Nov 07 2013
On Friday, 8 November 2013 at 04:06:22 UTC, Chris Cain wrote:So fix 2 and 3 and it works for getting x.Also, define `toOffset` in the template constraint.
Nov 07 2013
Strange. I'm getting a different error, but I'm still running 2.063.2. The error I get is `Error: cannot resolve type for t.opDispatch!("x")` What version are you running?I just updated to 2.064.2In any case, the reason apparently is multifold: 1. Apparently the proper error message isn't shown when using the property notation. (I'd have to check to see if it happens in 2.064 ... might be a fixed bug) 2. `.charAt(0)` doesn't exist for D's strings. You can just use bracket notation to access the index. 3. When args is empty (as it will be for a getter, when you call) args[0] doesn't exist, so `Error: array index [0] is outside array bounds [0 .. 0]` So fix 2 and 3 and it works for getting x. The reason I use a static if is to separate the cases where args has items and when it does not (since args[0] is invalid when args.length == 0), so that'll be necessary to get it to work.Okay 2 is fixed, leftovers of other languages in my mind. Also took care of 3 I think but I may still be missing something. Here is what I have now.. class Vector(int N, T) if (N <= 3) { T[N] data; this() { data[] = 0; } property ref T opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && toOffset(fieldName) < N) { int offset = fieldName[0 .. 1] - 'x'; if (args.length != 0) return data[offset]; else return data[offset] = args[0]; } } Same error.
Nov 07 2013
On Friday, 8 November 2013 at 04:28:31 UTC, Ross Hays wrote:Just reread and realized I glossed over your mention of static if. class Vector(int N, T) if (N <= 3) { T[N] data; this() { data[] = 0; } property ref T opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && toOffset(fieldName) < N) { int offset = fieldName[0 .. 1] - 'x'; static if (args.length != 0) return data[offset]; else return data[offset] = args[0]; } } Still the same problems.Strange. I'm getting a different error, but I'm still running 2.063.2. The error I get is `Error: cannot resolve type for t.opDispatch!("x")` What version are you running?I just updated to 2.064.2In any case, the reason apparently is multifold: 1. Apparently the proper error message isn't shown when using the property notation. (I'd have to check to see if it happens in 2.064 ... might be a fixed bug) 2. `.charAt(0)` doesn't exist for D's strings. You can just use bracket notation to access the index. 3. When args is empty (as it will be for a getter, when you call) args[0] doesn't exist, so `Error: array index [0] is outside array bounds [0 .. 0]` So fix 2 and 3 and it works for getting x. The reason I use a static if is to separate the cases where args has items and when it does not (since args[0] is invalid when args.length == 0), so that'll be necessary to get it to work.Okay 2 is fixed, leftovers of other languages in my mind. Also took care of 3 I think but I may still be missing something. Here is what I have now.. class Vector(int N, T) if (N <= 3) { T[N] data; this() { data[] = 0; } property ref T opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && toOffset(fieldName) < N) { int offset = fieldName[0 .. 1] - 'x'; if (args.length != 0) return data[offset]; else return data[offset] = args[0]; } } Same error.
Nov 07 2013
On Friday, 8 November 2013 at 04:28:31 UTC, Ross Hays wrote:class Vector(int N, T) if (N <= 3) { T[N] data; this() { data[] = 0; } property ref T opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && toOffset(fieldName) < N) { int offset = fieldName[0 .. 1] - 'x'; if (args.length != 0) return data[offset]; else return data[offset] = args[0]; } } Same error.Sorry, I forgot to mention in that post that you have "toOffset" in your template constraint, which means it will also never match. You'll have to define it or replace it with `fieldName[0] - 'x';` Also, you might not want to do `fieldName[0 .. 1]` because that's a slice (which is just another array of length 1). It won't do what you're expecting.
Nov 07 2013
Sorry, I forgot to mention in that post that you have "toOffset" in your template constraint, which means it will also never match. You'll have to define it or replace it with `fieldName[0] - 'x';` Also, you might not want to do `fieldName[0 .. 1]` because that's a slice (which is just another array of length 1). It won't do what you're expecting.And more stupid mistakes on my part. Thank you, still not 100% there with D but working on it. (though some of those mistakes were dumb even outside of D lol). class Vector(int N, T) if (N <= 3) { T[N] data; property ref T opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && fieldName[0] - 'x' < N) { int offset = fieldName[0] - 'x'; static if (args.length != 0) return data[offset]; else return data[offset] = args[0]; } } void main() { Vector!(2, float) t = new Vector!(2,float)(); writeln(t.x); } Another revision, still the same problems and I am pretty sure I am not forgetting anything this time... I am sure that is wrong too
Nov 07 2013
On Friday, 8 November 2013 at 04:38:23 UTC, Ross Hays wrote:Thank youNo problem. I'm glad we're making progress on it. And don't feel bad about mistakes while learning. They happen. Embrace them because they happen to all of us at first especially when we're juggling learning new syntaxes and tricks and such.class Vector(int N, T) if (N <= 3) { T[N] data; property ref T opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && fieldName[0] - 'x' < N) { int offset = fieldName[0] - 'x'; static if (args.length != 0) return data[offset]; else return data[offset] = args[0]; } }Okay, your static if's condition is swapped (args.length should be 0 if you're just returning data[offset]). Also, when you correct that, you'll run into an issue where it reveals that `data[offset] = args[0]` is not an lvalue (which is required for ref). Either change the return type to "auto" and remove the return for that branch... or, alternatively, you can split the statement into two: else { data[offset] = args[0]; return data[offset]; } and `data[offset]` will be an lvalue.
Nov 07 2013
Boom, that last few were the issues. I elected to just move the return for the setter onto a separate line, mostly because the idea of auto returning different types seem foreign to me... I have used auto plenty in C++11, but never like that and it just throws me off. But fixing those other mistakes we get: class Vector(int N, T) if (N <= 3) { T[N] data; this() { data[] = 0; } property ref T opDispatch(string fieldName, Args ...)(Args args) if (Args.length < 2 && fieldName.length == 1 && fieldName[0] - 'x' < N) { int offset = fieldName[0] - 'x'; static if (args.length == 0) { return data[offset]; } else { data[offset] = args[0]; return data[offset]; } } } void main() { Vector!(2, float) t = new Vector!(2,float)(); writeln(t.x); t.x = 4; writeln(t.x); } That works as intended, now if only it were useful.
Nov 07 2013
And let me say that I really do like that this works in D. I can't imagine doing anything like this in C++ (which is what I used primarily in the past). The only reason I joke about it being useless is it really only supports vectors of length 2 or 3 (technically 1 as well but that is not really a vector) and at that point I am tempted to say it is more efficient to just make a Vector2!float class or something of the sorts.
Nov 07 2013
On Fri, Nov 8, 2013 at 5:55 AM, Ross Hays <throwaway email.net> wrote:And let me say that I really do like that this works in D. I can't imagine doing anything like this in C++ (which is what I used primarily in the past). The only reason I joke about it being useless is it really only supports vectors of length 2 or 3 (technically 1 as well but that is not really a vector) and at that point I am tempted to say it is more efficient to just make a Vector2!float class or something of the sorts.Then the next step is to allow swizzling (sp?) :-) auto myVec = yourVec.yzx; // myVec.x = yourVec.y, myVec.y = yourVec.z, myVec.z = yourVec.x; auto myVec2 = yourVec.xxx; // and so on
Nov 07 2013
On 11/07/2013 10:05 PM, Philippe Sigaud wrote:On Fri, Nov 8, 2013 at 5:55 AM, Ross Hays <throwaway email.net> wrote:Manu Evans had reported either during his talk at DConf 2013 or during private conversations at the same conference that the technique has been used at Remedy Games. AliAnd let me say that I really do like that this works in D. I can't imagine doing anything like this in C++ (which is what I used primarily in the past). The only reason I joke about it being useless is it really only supports vectors of length 2 or 3 (technically 1 as well but that is not really a vector) and at that point I am tempted to say it is more efficient to just make a Vector2!float class or something of the sorts.Then the next step is to allow swizzling (sp?) :-) auto myVec = yourVec.yzx; // myVec.x = yourVec.y, myVec.y = yourVec.z, myVec.z = yourVec.x; auto myVec2 = yourVec.xxx; // and so on
Nov 25 2013
Okay here is something I was hoping for some general clarification on related to this and maybe you can help me sort some things out. The opDispatch method has a template parameter of string fieldName. In C++, templates are actually compiled so each different use of a template class is compiled into its own thing. With D, if this is the case I imagine I would not be able to access the template parameter of string? If I can then why does it need to be a template parameter at all and not a normal parameter.
Nov 07 2013
On Friday, 8 November 2013 at 05:10:57 UTC, Ross Hays wrote:Okay here is something I was hoping for some general clarification on related to this and maybe you can help me sort some things out. The opDispatch method has a template parameter of string fieldName. In C++, templates are actually compiled so each different use of a template class is compiled into its own thing. With D, if this is the case I imagine I would not be able to access the template parameter of string? If I can then why does it need to be a template parameter at all and not a normal parameter.Yes, D is the same as C++ in that each unique template instantiation is compiled differently. Almost like copy & paste where you replace the template parameters with what was passed in. What do you mean by "not be able to access the template parameter of string"? If you just mean whether you can use it or not, well, you can. It's available to you both at compile time and at run time if it's a template parameter like that. In the case of opDispatch, the compiler essentially rewrites all unknown calls (if `x.foo()` doesn't exist, then change it to `x.opDispatch!"foo"()`). One of the reasons why passing it in as a template parameter is better than a regular parameter is that opDispatch!s can specialize at compile time so that it doesn't result in any runtime performance hit. You can look at it like this: with a compile-time opDispatch means that `opDispatch!"x"; opDispatch!"y";` and so on is (essentially) binary equivalent to you having actually hand-written those functions (that is just a hand-written `x` or `y` property). So, there's no runtime overhead of conditionals, no allocating strings, no actual passing of parameters, or, potentially, even the creation of that offset variable (so, it doesn't even do the subtraction at runtime because it can figure it out at compile time and just fill it in as if it were a literal). One tiny place of improvement for your code, however, is if you changed it to `static immutable offset = ...;` because that helps the compiler know to do that operation at compile time. That said, a Sufficiently Smart Compiler (tm) would be able to do it regardless of the extra hint, but AFAIK DMD typically doesn't do all of the optimizations it could.
Nov 07 2013
Chris Cain:One tiny place of improvement for your code, however, is if you changed it to `static immutable offset = ...;` because that helps the compiler know to do that operation at compile time.The idiomatic way to do that is to use "enum offset = ...;". Bye, bearophile
Nov 08 2013
On Friday, 8 November 2013 at 03:35:34 UTC, Ross Hays wrote:Awesome that seems to do what I was going for. I had tried a similar approach with property dispatch and the subtraction of 'x', but I had left out the static if and had the opDispatch returning a ref of the entry in the array (so there would just be the one property still) but that resulted in "Error: no property 'x' for type 'test.Vector!(2, float).Vector'". This is interesting, though probably not a very safe way to handle vectors in the real world (even more so if they are going to be vectors of more than length 3).Indeed. Presumably you could modify it to shift the starting letter (which is currently hardcoded as 'x') leftward as N grows bigger to allow it to support more. But really, you would probably be better to just use tuples. Technically a Vector is just a Tuple!(T, "x", T, "y") so you could probably do better by writing some metaprogramming to make one of those if you wanted a short-hand for it. Otherwise just doing "alias Vector2(T) = Tuple!(T, "x", T, "y");" would work great in 2.064.2, if I remember the syntax correctly. Then Vector2!float would work. Similarly Vector3 could be made and so on. http://dlang.org/changelog.html#eponymous_template
Nov 07 2013