www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - opCast / operator overloading with additional template arguments

reply Paul <paultjeadriaanse gmail.com> writes:
Is there a way to have additional template arguments in operator 
overloads?
The problem I'm having is mostly caused by casting a templated 
struct to other templated structs. I had the following code;

 T opCast(T)() const if (is(T : Vec!(vecsize, S), S)) {
 	T converted;
 	static foreach (i; 0 .. vecsize)
 		converted.content[i] = cast(S) content[i];
 	return converted;
 }
When I try to use this, I get the error 'undefined identifier S'. Alternatively using:
 T opCast(T, S)() . . .
causes the error 'template instance opCast!(Vec!(2, double)) does not match template declaration opCast(T, S)()' I found using 'alias S = typeof(content[0])' works as a kind of ducttape alternative, however this seems like a codesmell to me, and I fear it won't scale well either. Am I missing a simple solution? And why is there no automatic argument deduction in this scenario when compared to normal function calls? (if the above opcast is translated to 'foo.opCast!T`)
Jan 10
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/10/21 4:09 PM, Paul wrote:

 Is there a way to have additional template arguments in operator 
overloads? I haven't tried that but the following method seems to work for you. You don't show complete code; so, I hope I came up with something that reflects your case. import std; struct S(T) { auto opCast(U)() const if (isInstanceOfS!U) { writefln!"Converting from %s to %s"(S.stringof, U.stringof); return U.init; } } enum isInstanceOfS(T) = isInstanceOf!(S, T); void main() { auto a = S!int(); auto b = cast(S!double)a; // Works auto c = a.to!(S!double); // This works too } I needed the isInstanceOfS wrapper over std.traits.isInstanceOf because I could not use isInstanceOf inside the definition of S because the symbol 'S' does not mean "the template S", but the specific instantiation of it e.g. S!int. Outside, S means "the template S". Ali
Jan 10
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Monday, 11 January 2021 at 00:25:36 UTC, Ali Çehreli wrote:
 You don't show complete code; so, I hope I came up with 
 something that reflects your case.
Thank you, sadly S (S2 now) is not any specific type, sorry I'll paste more of my file, I hope that's ok. (Sidenote, I'm not sure it's the most elegant approach to have a templated union like this, and I left out some unnecessary stuff like 'opBinary')
 version (HoekjeD_Double) {
 	private alias standard_accuracy = double;
 } else {
 	private alias standard_accuracy = float;
 }
 
 struct Vec(int size, S = standard_accuracy) {
 	union {
 		S[size] content;
 		static if (size >= 1) {
 			struct {
 				S x;
 				static if (size >= 2) {
 					S y;
 					static if (size >= 3) {
 						S z;
 						static if (size >= 4)
 							S w;
 					}
 				}
 			}
 		}
 	}
 
 	T opCast(T)() const if (is(T : Vec!(size, S2), S2)) {
 		T converted;
 		static foreach (i; 0 .. size)
 			converted.content[i] = cast(S2) content[i];
 		return converted;
 	}
 }
Jan 10
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/10/21 5:09 PM, Paul wrote:

 I'll paste more of my file, I hope that's ok.
Not only ok but much appreciated. :)
     T opCast(T)() const if (is(T : Vec!(size, S2), S2)) {
The is expression can be so complicated that I used a different approach below. I left notes in capital letters below. Especially cast(S2) looks wrong to me: import std; version (HoekjeD_Double) { private alias standard_accuracy = double; } else { private alias standard_accuracy = float; } struct Vec(int size, S = standard_accuracy) { // You could add this instead of getting the type from // 'content' as in how S2 is aliased in opCast below: // // alias AccuracyType = S; union { S[size] content; static if (size >= 1) { struct { S x; static if (size >= 2) { S y; static if (size >= 3) { S z; static if (size >= 4) S w; } } } } } T opCast(T)() const // CHANGED: if (isInstanceOfVec!T && T.init.content.length == size) { // ADDED: alias S2 = typeof(T.init.content[0]); T converted; static foreach (i; 0 .. size) // OBSERVATION: Should the cast below be S? converted.content[i] = cast(S2) content[i]; return converted; } } enum isInstanceOfVec(T) = isInstanceOf!(Vec, T); void main() { auto a = Vec!(42, float)(); auto b = a.to!(Vec!(42, double)); } Ali
Jan 10
next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/10/21 6:37 PM, Ali =C3=87ehreli wrote:

        // OBSERVATION: Should the cast below be S?
        converted.content[i] =3D cast(S2) content[i];
I take that back. Yes, it should be S2. (I've been off lately. :) ) Ali
Jan 10
prev sibling parent reply Paul <paultjeadriaanse gmail.com> writes:
On Monday, 11 January 2021 at 02:37:24 UTC, Ali Çehreli wrote:
     T opCast(T)() const if (is(T : Vec!(size, S2), S2)) {
 The is expression can be so complicated that I used a different 
 approach below.
   if (isInstanceOfVec!T &&
       T.init.content.length == size) {
     // ADDED:
     alias S2 = typeof(T.init.content[0]);
Is it ok to use .init even though we don't need an instantiated value?
Jan 10
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/10/21 7:27 PM, Paul wrote:

 On Monday, 11 January 2021 at 02:37:24 UTC, Ali =C3=87ehreli wrote:
     T opCast(T)() const if (is(T : Vec!(size, S2), S2)) {
 The is expression can be so complicated that I used a different
 approach below.
   if (isInstanceOfVec!T &&
       T.init.content.length =3D=3D size) {
     // ADDED:
     alias S2 =3D typeof(T.init.content[0]);
Is it ok to use .init even though we don't need an instantiated value?=
Yes, .init useful in many templates. However, you don't actually use=20 that object because typeof does not evaluate the expression, just=20 produces the type of it. Related, the content[0] expression might be seen as invalid code because = the array may not have any elements at all but again, there is no access = to element 0. There are other ways e.g. import std.range; alias S2 =3D ElementType!(typeof(T.init.content)); Ali
Jan 11
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/10/21 7:09 PM, Paul wrote:
 Is there a way to have additional template arguments in operator overloads?
 The problem I'm having is mostly caused by casting a templated struct to 
 other templated structs. I had the following code;
 
 T opCast(T)() const if (is(T : Vec!(vecsize, S), S)) {
     T converted;
     static foreach (i; 0 .. vecsize)
         converted.content[i] = cast(S) content[i];
     return converted;
 }
When I try to use this, I get the error 'undefined identifier S'. Alternatively using:
 T opCast(T, S)() . . .
causes the error 'template instance opCast!(Vec!(2, double)) does not match template declaration opCast(T, S)()' I found using 'alias S = typeof(content[0])' works as a kind of ducttape alternative, however this seems like a codesmell to me, and I fear it won't scale well either. Am I missing a simple solution? And why is there no automatic argument deduction in this scenario when compared to normal function calls? (if the above opcast is translated to 'foo.opCast!T`)
It has nothing to do with operator overloads. types inferred inside template constraints are not accessible inside the function. The awkward solution is to use the same is expression inside the function: static if(is(T : Vec!(vecsize, S), S)) {} // now you can use S I would think though, that this should work: T opCast(T : Vec!(vecsize, S), S)() But I don't have your full code to test. -Steve
Jan 10
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Monday, 11 January 2021 at 00:48:49 UTC, Steven Schveighoffer 
wrote:
 I would think though, that this should work:

 T opCast(T : Vec!(vecsize, S), S)()
Oh wouw, this seems to work perfectly! Awesome thanks ^^ Any Idea why
 T opCast(T, S)() const if (is(T : Vec!(grootte, S))) {
yields the error
 template instance opCast!(Vec!(2, double)) does not match 
 template declaration opCast(T, S)()
while your suggestion does not? It seems to me it should match equally well. Also I had no clue types inferred in constraints were inaccessibly, I'll try to keep that in mind, though I wonder, is there is any specific reason for that? Then again since your example works inferred values shouldnt be necessary in constraints anyway. (On that note, is there per chance something like the underscore '_' as in python? In some cases I don't care for all the template arguments inside an is expression (with the (a:b, c) version))
Jan 10
parent Paul Backus <snarwin gmail.com> writes:
On Monday, 11 January 2021 at 03:40:41 UTC, Paul wrote:
 On Monday, 11 January 2021 at 00:48:49 UTC, Steven 
 Schveighoffer wrote:
 I would think though, that this should work:

 T opCast(T : Vec!(vecsize, S), S)()
Oh wouw, this seems to work perfectly! Awesome thanks ^^ Any Idea why
 T opCast(T, S)() const if (is(T : Vec!(grootte, S))) {
yields the error
 template instance opCast!(Vec!(2, double)) does not match 
 template declaration opCast(T, S)()
while your suggestion does not? It seems to me it should match equally well.
The compiler does not look at template constraints until after it figures out what all the template arguments are. So, in your version, it sees T opCast(T, S) // ... opCast!(Vec!(2, double)) ...and is able to deduce that `T = Vec!(2, double)`, but doesn't have any way to figure out what `S` is, so it gives up.
Jan 10