digitalmars.D.learn - Operator Overloading and boilerplate code
- Loopback (105/105) Jul 02 2011 Hi!
- Timon Gehr (39/39) Jul 02 2011 Your example with reduced boilerplate code:
- Loopback (77/77) Jul 03 2011 Thank you!
- Loopback (18/18) Jul 04 2011 I've researched a bit though I still haven't come up with a solution.
- Ali =?iso-8859-1?q?=C7ehreli?= (25/51) Jul 04 2011 Here is a simple form of the same problem:
- Loopback (11/62) Jul 05 2011 Hmm... Interesting. Thank you for clarifying and explaining that!
- Ali =?iso-8859-1?q?=C7ehreli?= (25/97) Jul 05 2011 I don't want to look like brushing off the problem but having many
- Loopback (14/22) Jul 05 2011 That might be true. I just did what felt most convenient, but perhaps
Hi! I've made a two dimensional vector structure (x, y) which is very primitive, the only real thing that I want to do with this structure is support for nearly all operators and support for D3DXVECTOR2 and POINT structures. While writing this structure the boilerplate code has steadily increased. Since I am new to D I mainly wanted to ask if there are any solutions to prevent this amount of boilerplate code with mixins etc... The unittest, also, fail to compile. When I am trying to use the in-place assignment operator the compiler complains saying this; Error: function dx.directx.DVECTOR2.opAssign (ref const const(DVECTOR2) rhs) is not callable using argument types (DVECTOR2) Error: cannot implicitly convert expression (this.opBinary(rhs)) of type DVECTOR2 to float Error: template instance dx.directx.DVECTOR2.opOpAssign!("+") error instantiating The first two errors origins from the last opOpAssign operator (taking a float argument) whilst the last error is from the "test += 2f" statement. struct DVECTOR2 { float x = 0f; float y = 0f; this(float x, float y) { this.x = x; this.y = y; } this(float xy) { this.x = xy; this.y = xy; } // Implement D3DXVECTOR2 support this(const ref D3DXVECTOR2 vec) { this.x = vec.x; this.y = vec.y; } // Implement POINT support this(const ref POINT pnt) { this.x = pnt.x; this.y = pnt.y; } // Return a inverse vector DVECTOR2 opUnary(string op)() if(op == "-") { return DVECTOR2(-x, -y); } // The Binary Operators DVECTOR2 opBinary(string op)(DVECTOR2 rhs) { return DVECTOR2(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y")); } DVECTOR2 opBinary(string op)(D3DXVECTOR2 rhs) { return opBinary!op(DVECTOR2(rhs)); } DVECTOR2 opBinary(string op)(POINT rhs) { return opBinary!op(DVECTOR2(rhs)); } DVECTOR2 opBinary(string op)(float rhs) { return opBinary!op(DVECTOR2(rhs)); } // Right Operators Support DVECTOR2 opBinaryRight(string op)(DVECTOR2 lhs) { return lhs.opBinary!op(this); } DVECTOR2 opBinaryRight(string op)(D3DXVECTOR2 lhs) { return DVECTOR2(lhs).opBinary!op(this); } DVECTOR2 opBinaryRight(string op)(POINT lhs) { return DVECTOR2(lhs).opBinary!op(this); } DVECTOR2 opBinaryRight(string op)(float lhs) { return DVECTOR2(lhs).opBinary!op(this); } // Assignment Operator ref DVECTOR2 opAssign(const ref DVECTOR2 rhs) { x = rhs.x; y = rhs.y; return this; } ref DVECTOR2 opAssign(const ref D3DXVECTOR2 rhs) { return (this.opAssign(DVECTOR2(rhs))); } ref DVECTOR2 opAssign(const ref POINT rhs) { return (this.opAssign(DVECTOR2(rhs))); } ref DVECTOR2 opAssign(float rhs) { return (this.opAssign(DVECTOR2(rhs))); } // In-Place Assignment Operators ref DVECTOR2 opOpAssign(string op)(DVECTOR2 rhs) { return (this.opAssign(opBinary!op(rhs))); } ref DVECTOR2 opOpAssign(string op)(D3DXVECTOR2 rhs) { return (this.opAssign(opBinary!op(rhs))); } ref DVECTOR2 opOpAssign(string op)(POINT rhs) { return (this.opAssign(opBinary!op(rhs))); } // This is where the two first errors occur ref DVECTOR2 opOpAssign(string op)(float rhs) { return (this.opAssign(opBinary!op(rhs))); } // Operator Casts D3DXVECTOR2 opCast(T)() if(is(T == D3DXVECTOR2)) { return D3DXVECTOR2(x, y); } POINT opCast(T)() if(is(T == POINT)) { return POINT(cast(int) x, cast(int) y); } } unittest { DVECTOR2 test = DVECTOR2(2f, 2f); test += 2f; // This is where the last error occurs assert(test.x == 4f); }
Jul 02 2011
Your example with reduced boilerplate code: struct DVECTOR2 { template Accepts(T){enum Accepts=is(T==DVECTOR2) || is(T==float) || is(T==D3DXVECTOR2) || is(T==POINT);} template isScalar(T){enum isScalar=is(T==float);} float x = 0f, y=0f; this()(float x, float y) { this.x = x; this.y = y; } this()(float xy) { this(xy,xy); } // Implement D3DXVECTOR2 and POINT support this(T)(T arg) if(Accepts!T && !isScalar!T) { this.x = arg.x; this.y = arg.y; } DVECTOR2 opUnary(string op)() if(op == "-") { return DVECTOR2(-x, -y); } DVECTOR2 opBinary(string op,T)(T rhs) if(Accepts!T) { enum rx="rhs"~isScalar!T?"":".x", ry=isScalar!T?"":".y"; return DVECTOR2(mixin("x" ~ op ~ "rhs"~rx), mixin("y" ~ op ~ "rhs"~ry)); } DVECTOR2 opBinaryRight(string op,T)(T lhs) if(Accepts!T) { return DVECTOR2(lhs).opBinary!op(this); } ref DVECTOR2 opAssign(T)(T rhs) if(Accepts!T) { static if(isScalar!T) x=y=rhs; else x=rhs.y, y=rhs.y; return this; } ref DVECTOR2 opOpAssign(string op,T)(T rhs) if(Accepts!T) { return(this.opAssign(opBinary!op(rhs))); } T opCast(T)() if(Accepts!T && !isScalar!T) { return T(x, y); } } unittest { DVECTOR2 test = DVECTOR2(2f, 2f); test += 2f; assert(test.x == 4f); } Should work. Cheers, -Timon
Jul 02 2011
Thank you! How excellent you handled the boilerplate code. Works perfectly fine as well, except one little thing. I cannot declare this DVECTOR2 structure immutable because the compiler complains saying this: Error: variable __ctmp1485 cannot be read at compile time Error: cannot evaluate __ctmp1485.this(0F,1F,0F) at compile time As I mentioned before, I am new to D and I do not understand why the compiler cannot evaluate the expression at compile time. One more thing which I also am wondering is how opCast really works. To trigger opCast you need to do an explicit cast (tell me if I'm wrong) and when you do this, does it also work for cases like this: cast(D3DXVECTOR2) &vector2 Does this code trigger the defined opCast (vector2 is a DVECTOR2 structure in this case) or do I have to define a new opCast in cases where the address operator is used or is the compiler able to distinguish the cases and cast opCast before the address operator is evaluated? struct DVECTOR2 { // Controls that the parameter is a valid type template Accepts(T) { enum Accepts = is(T == DVECTOR2) || is(T == float) || is(T == D3DXVECTOR2) || is(T == POINT); } // Whether the parameter is a float or not template isScalar(T) { enum isScalar = is(T == float); } // The Variables float x = 0f; float y = 0f; // Default Constructor this()(float x, float y) { this.x = x; this.y = y; } // Float Constructor this()(float xy) { this(xy, xy); } // Implement D3DXVECTOR2 and POINT support this(T)(T arg) if(Accepts!T && !isScalar!T) { this(arg.tupleof); } // Inverse the vector DVECTOR2 opUnary(string op)() if(op == "-") { return DVECTOR2(-x, -y); } // Binary Operations DVECTOR2 opBinary(string op, T)(T rhs) if(Accepts!T) { enum rx = isScalar!T ? "" : ".x"; enum ry = isScalar!T ? "" : ".y"; return DVECTOR2(mixin("x" ~ op ~ "rhs" ~ rx), mixin("y" ~ op ~ "rhs" ~ ry)); } // Right Binary Operator DVECTOR2 opBinaryRight(string op, T)(T lhs) if(Accepts!T) { return DVECTOR2(lhs).opBinary!op(this); } // Assign Operator ref DVECTOR2 opAssign(T)(T rhs) if(Accepts!T) { static if(isScalar!T) x = y = rhs; else { x = rhs.x; y = rhs.y; } return this; } // In-Place Assignment Operators ref DVECTOR2 opOpAssign(string op, T)(T rhs) if(Accepts!T) { return(this.opAssign(opBinary!op(rhs))); } // Cast Operators (to D3DXVECTOR2 and POINT) T opCast(T)() if(Accepts!T && !isScalar!T) { return T(x, y); } }
Jul 03 2011
I've researched a bit though I still haven't come up with a solution. Since the problem lies within (the most simple) constructor, I tried to modify it for another outcome. If I supplied a generic parameter to the pre-constructor the "Cannot evaluate at compile time" message disappeared but two new errors appeared instead. This is what I modified: this()(float x, float y, float z) => this(T)(float x, float y, float z) If I use this code instead, I get two other errors appearing: Error: template test.DVECTOR2.__ctor(T) does not match any function template declaration This error and another one (individual to each statement) appears in the following code statements: Error: template test.DVECTOR2.__ctor(T) cannot deduce template function from argument types !()(float,float) DVECTOR2 m_zoom = DVECTOR2(2f, 2f); Error: template test.DVECTOR2.__ctor(T) cannot deduce template function from argument types !()(immutable(float),const(float)) immutable DVECTOR2 m_UP_DIR = DVECTOR2(0f, 1f, 0f);
Jul 04 2011
On Tue, 05 Jul 2011 02:44:03 +0200, Loopback wrote:I've researched a bit though I still haven't come up with a solution. Since the problem lies within (the most simple) constructor, I tried to modify it for another outcome. If I supplied a generic parameter to the pre-constructor the "Cannot evaluate at compile time" message disappeared but two new errors appeared instead. This is what I modified: this()(float x, float y, float z) => this(T)(float x, float y, float z) If I use this code instead, I get two other errors appearing: Error: template test.DVECTOR2.__ctor(T) does not match any function template declaration This error and another one (individual to each statement) appears in the following code statements: Error: template test.DVECTOR2.__ctor(T) cannot deduce template function from argument types !()(float,float) DVECTOR2 m_zoom = DVECTOR2(2f, 2f); Error: template test.DVECTOR2.__ctor(T) cannot deduce template function from argument types !()(immutable(float),const(float)) immutable DVECTOR2 m_UP_DIR = DVECTOR2(0f, 1f, 0f);Here is a simple form of the same problem: struct S { this(T)(double d) {} } void main() { auto o = S(1.5); } Error: template deneme.S.__ctor(T) does not match any function template declaration Error: template deneme.S.__ctor(T) cannot deduce template function from argument types !()(double) The compiler is right: What should T be there? int? string? MyClass? I've realized again that I don't know how to specify the template parameter for the constructor. The following attempt fails as the compiler thinks S itself is a template: auto o = S!string(1.5); Error: template instance S is not a template declaration, it is a struct And if I try to be smart after the error message, this seg faults the compiler: auto o = S.__ctor!string(1.5); Ali
Jul 04 2011
On 2011-07-05 03:11, Ali Çehreli wrote:On Tue, 05 Jul 2011 02:44:03 +0200, Loopback wrote:Hmm... Interesting. Thank you for clarifying and explaining that! I guess supplying T to the constructor when the parameters are already known to avoid compiler errors is not a solution then. Seems to me as if its only aggravates things. Though is there no solution nor any workarounds for this problem? I've attempted to use two different types of constructors and both appeared to be very limited, and I do not believe that this is the case. If you use a generic constructor is there no possible way to use it in cases where immutable and const is involved? Or is there a page that I have missed perhaps?I've researched a bit though I still haven't come up with a solution. Since the problem lies within (the most simple) constructor, I tried to modify it for another outcome. If I supplied a generic parameter to the pre-constructor the "Cannot evaluate at compile time" message disappeared but two new errors appeared instead. This is what I modified: this()(float x, float y, float z) => this(T)(float x, float y, float z) If I use this code instead, I get two other errors appearing: Error: template test.DVECTOR2.__ctor(T) does not match any function template declaration This error and another one (individual to each statement) appears in the following code statements: Error: template test.DVECTOR2.__ctor(T) cannot deduce template function from argument types !()(float,float) DVECTOR2 m_zoom = DVECTOR2(2f, 2f); Error: template test.DVECTOR2.__ctor(T) cannot deduce template function from argument types !()(immutable(float),const(float)) immutable DVECTOR2 m_UP_DIR = DVECTOR2(0f, 1f, 0f);Here is a simple form of the same problem: struct S { this(T)(double d) {} } void main() { auto o = S(1.5); } Error: template deneme.S.__ctor(T) does not match any function template declaration Error: template deneme.S.__ctor(T) cannot deduce template function from argument types !()(double) The compiler is right: What should T be there? int? string? MyClass? I've realized again that I don't know how to specify the template parameter for the constructor. The following attempt fails as the compiler thinks S itself is a template: auto o = S!string(1.5); Error: template instance S is not a template declaration, it is a struct And if I try to be smart after the error message, this seg faults the compiler: auto o = S.__ctor!string(1.5); Ali
Jul 05 2011
On Tue, 05 Jul 2011 16:20:44 +0200, Loopback wrote:On 2011-07-05 03:11, Ali Çehreli wrote:I don't want to look like brushing off the problem but having many constructors make the code complicated. For example, it may be confusing which constructor gets called here: auto d = DVECTOR2(1.5); Are we setting just x or both x and y? Especially when we know that DVECTOR2 is a struct and that structs have this feature of assigning default values to the trailing unspecified members (at least in some cases), I think a factory function would be better in this case: auto d = square_DVECTOR2(1.5); Now we know that both x and y will be 1.5. Same can be said for some of the other constructors. It is not difficult at all for the caller to give us what we want; and it is clearer: D3DXVECTOR2 d3; // ... auto d = DVECTOR2(d3.tupleof); (I think this is in line with Kevlin Henney's "Parameterize from Above" guideline/pattern/idiom/etc. :))struct S { this(T)(double d) {} } void main() { auto o = S(1.5); } Error: template deneme.S.__ctor(T) does not match any function template declaration Error: template deneme.S.__ctor(T) cannot deduce template function from argument types !()(double) The compiler is right: What should T be there? int? string? MyClass? I've realized again that I don't know how to specify the template parameter for the constructor. The following attempt fails as the compiler thinks S itself is a template: auto o = S!string(1.5); Error: template instance S is not a template declaration, it is a struct And if I try to be smart after the error message, this seg faults the compiler: auto o = S.__ctor!string(1.5); AliHmm... Interesting. Thank you for clarifying and explaining that! I guess supplying T to the constructor when the parameters are already known to avoid compiler errors is not a solution then. Seems to me as if its only aggravates things. Though is there no solution nor any workarounds for this problem? I've attempted to use two different types of constructors and both appeared to be very limited, and I do not believe that this is the case.If you use a generic constructor is there no possible way to use it in cases where immutable and const is involved? Or is there a page that I have missed perhaps?D2 has changed the meaning of inout to mean something like "templatize just the mutable/const/immutable qualification of the parameter" but it is not implemented fully yet. Look at "Inout Functions" on the Functions spec: http://d-programming-language.org/function.htmlstruct DVECTOR2 { // Controls that the parameter is a valid type template Accepts(T) {enum Accepts = is(T == DVECTOR2) || is(T == float) || is(T == D3DXVECTOR2) || is(T == POINT); } // Whether the parameter is a float or not template isScalar(T) { enum isScalar = is(T == float); } // The Variables float x = 0f; float y = 0f; // Default Constructor this()(float x, float y) { this.x = x; this.y = y; } // Float Constructor this()(float xy) { this(xy, xy); } // Implement D3DXVECTOR2 and POINT support this(T)(T arg) if (Accepts!T && !isScalar!T) { this(arg.tupleof); }Ali
Jul 05 2011
On 2011-07-05 18:05, Ali Çehreli wrote:I don't want to look like brushing off the problem but having many constructors make the code complicated. For example, it may be confusing which constructor gets called here: auto d = DVECTOR2(1.5);That might be true. I just did what felt most convenient, but perhaps that is not always the solution.D2 has changed the meaning of inout to mean something like "templatize just the mutable/const/immutable qualification of the parameter" but it is not implemented fully yet. Look at "Inout Functions" on the Functions spec:Foolish of me to forget about inout functions. Is there any possibility though that the inout tag offers a solution to my initial problem, where the constructor couldn't be evaluted at compile time? It feels a bit redundant if you would have to have unique constructors just to enable support for immutable instantiations of your class, or perhaps this lies within the use of templates and their generic parameters? I've been at this problem for over a day and it feels awful to be left with no choice and move away from using templates and instead having walls of boilerplate code just to support immutable and const instantiations of one's structure.
Jul 05 2011