I'm trying out the classic way of making a generic vector using the new static abstract abilities (like INumber<T>) that has been added to dotnet.
I am struggling to make it so the math can be abstracted, while being used for structs.
This is what I have
public interface IVec2<T> where T : INumber<T>
{
T X { get; set; }
T Y { get; set; }
public static T Dot(IVec2<T> left, IVec2<T> right) => T.Create(left.X * right.X + left.Y * right.Y);
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public record struct Vec2D(double X, double Y) : IVec2<double>
{
public static Vec2D operator +(Vec2D left, Vec2D right) => left with { X = left.X + right.X, Y = left.Y + right.Y };
public static Vec2D operator -(Vec2D left, Vec2D right) => left with { X = left.X - right.X, Y = left.Y - right.Y };
public static Vec2D operator *(Vec2D left, double value) => left with { X = left.X * value, Y = left.Y * value };
public static Vec2D operator *(Vec2D left, Vec2D right) => left with { X = left.X * right.X, Y = left.Y * right.Y };
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public record struct Vec2F(float X, float Y) : IVec2<float>
{
public static Vec2F operator +(Vec2F left, Vec2F right) => left with { X = left.X + right.X, Y = left.Y + right.Y };
public static Vec2F operator -(Vec2F left, Vec2F right) => left with { X = left.X - right.X, Y = left.Y - right.Y };
public static Vec2F operator *(Vec2F left, float value) => left with { X = left.X * value, Y = left.Y * value };
public static Vec2F operator *(Vec2F left, Vec2F right) => left with { X = left.X * right.X, Y = left.Y * right.Y };
}
But this is what I want
// Not sure how to make TSelf work here
public interface IVec2<T> where T : INumber<T>
{
T X { get; set; }
T Y { get; set; }
// I'm doing this wrong, I need the type to be a struct, or some kind of TSelf
public static TSelf operator +(TSelf left, TSelf right) => left with { X = left.X + right.X, Y = left.Y + right.Y };
public static TSelf operator -(TSelf left, TSelf right) => left with { X = left.X - right.X, Y = left.Y - right.Y };
public static TSelf operator *(TSelf left, T value) => left with { X = left.X * value, Y = left.Y * value };
public static TSelf operator *(TSelf left, ITSelf right) => left with { X = left.X * right.X, Y = left.Y * right.Y };
public static T Dot(IVec2<T> left, IVec2<T> right) => T.Create(left.X * right.X + left.Y * right.Y);
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public record struct Vec2D(double X, double Y) : IVec2<double>
{
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public record struct Vec2F(float X, float Y) : IVec2<float>
{
}
I understand the code is confusing because I have been trying to work around getting it to return structs, but it should highlight what I'm trying to do, which is:
- Make it work with any
INumberable - Offload the math functions into a parent interface
- Make it such that it returns the same vector type for certain operations (meaning I want a function that returns itself).
As you can see, doing stuff like a dot product are easy for me to do because it returns T rather than IVec2<T>. As soon as I reach IVec2<T> then I start running into problems because I don't know how to constrain it to itself.
Is what I want possible?
Also I understand there are pitfalls with this (like if something inherits from it and has its own state, and then it doesn't get updated in one of the parent functions). Despite being a major concern in the real world, for the sake of this question, assume that will never happen.
An example of something that can begin to work would be like this
public static TSelf Abs<TSelf>(TSelf vec) where TSelf : IVec2<T>, new() =>
new() { X = T.Abs(vec.X), Y = T.Abs(vec.Y) };
but I don't know how to make this work generically for things like operator+ (and yes, there are some major problems with this, but again ignore the real world implications).