Thread overview
opCast / operator overloading with additional template arguments
Jan 11, 2021
Paul
Jan 11, 2021
Ali Çehreli
Jan 11, 2021
Paul
Jan 11, 2021
Ali Çehreli
Jan 11, 2021
Ali Çehreli
Jan 11, 2021
Paul
Jan 11, 2021
Ali Çehreli
Jan 11, 2021
Paul
Jan 11, 2021
Paul Backus
January 11, 2021
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`)
January 10, 2021
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

January 10, 2021
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
January 11, 2021
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;
> 	}
> }
January 10, 2021
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

January 10, 2021
On 1/10/21 6:37 PM, Ali Çehreli wrote:

>        // OBSERVATION: Should the cast below be S?
>        converted.content[i] = cast(S2) content[i];

I take that back. Yes, it should be S2. (I've been off lately. :) )

Ali


January 11, 2021
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?
January 11, 2021
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))
January 11, 2021
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.
January 11, 2021
On 1/10/21 7:27 PM, Paul wrote:

> 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?

Yes, .init useful in many templates. However, you don't actually use that object because typeof does not evaluate the expression, just 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 = ElementType!(typeof(T.init.content));

Ali