Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ style templates in C#, possible in any way?

I want to have 2d vector classes for every primitive type.

Right now, to assure the best runtime performance and be able to use many utility functions, I need to have a separate class for every primitive (Vector2Int, Vector2Float, Vector2Long, etc).

It's just a lot of copy-pasting, and if I have to make a change I have to remember to do it in every class and in every utility function.

Is there anything that lets me write something like C++ templates (or is there any way I can create it)?

I created a little concept to show you how this would work:

// compile is a keyword I just invented for compile-time generics/templates

class Vector2<T> compile T : int, float, double, long, string
{
    public T X { get; set; }
    public T Y { get; set; }

    public T GetLength() 
    {
        return Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2));
    }
}

// during compilation, code will be automatically generated
// as if someone manually replaced T with the types specified after "compile T : "
/*
    VALID EXAMPLE (no compilation errors):

    autogenerated class Vector2<int>
    {
        public int X { get; set; }
        public int Y { get; set; }

        public int GetLength() 
        {
            return Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2));
        }
    }



    UNVALID EXAMPLE (build failed, compilation errors):

    autogenerated class Vector2<string>
    {
        public string { get; set; } // ok
        public string { get; set; } // ok

        public string GetLength() 
        {
            return Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2)); // error! string cannot be used with Math.Pow()
                                             // and Math.Sqrt doesn't accept string type
        }
    }
*/

Is there some clever way to implement this, or is this completely impossible?


Sorry for not being very clear, but let me explain what the problem is.

Consider using normal C# generics. The GetLength() method wouldn't compile, because all the types I want to use (int, float, double, long) would require to share an interface which Math.Pow() should accept as a parameter.

Literally substituting the "T" token with the type names would solve this problem, increase flexibility, reach hand-written code performance and speed up development.


I made my own template generator, which generates C# code by writing C# code :) http://www.youtube.com/watch?v=Uz868MuVvTY

like image 589
Vittorio Romeo Avatar asked Sep 09 '12 21:09

Vittorio Romeo


2 Answers

Unfortunately, generics in C# are very different than templates in C++. In order to accomplish this, a shared interface (such as IArithmetic) would have to exist (which has been highly requested, but not implemented)* for the different types, and this doesn't in the framework now.

This can be done via code generation and T4 templates, however, but it requires generating the code for each type based off a shared "template".

*Note: The connect request appears to be blocked, at least temporarily.

like image 95
Reed Copsey Avatar answered Oct 05 '22 04:10

Reed Copsey


Two solutions to this problem:

  1. Make an abstract class or interface calculator[t] and implement it for the types you care about. Pass an instance of the calculator to your vector classes so they can use it to do mathematical operations.

  2. Using expression trees, you can actually create a static class calculator[t] that has methods like add, pow, etc. in the static constructor, you can compile dynamic expressions and the have the static methods call these compiled lambdas. With this approach, you don't have to implement the calculator for each type or pass it around (since its static).

For example:

public static class Calculator<T> {

   public static readonly Func<T, T, T> Add;
   public static readonly Func<T, T, T> Pow;

   static Calculator() {
       var p1 = Expression.Parameter(typeof(T));
       var p2 = Expression.Parameter(typeof(T));
       var addLambda = Expression.Lambda<Func<T, T, T>>(Expression.Add(p1, p2), p1, p2);
       Add = addLambda.Compile();

       // looks like the only Pow method on Math works for doubles
       var powMethod = typeof(Math).GetMethod("Pow", BindingFlags.Static | BindingFlags.Public);
       var powLambda = Expression.Lambda<Func<T, T, T>>(
           Expression.Convert(
               Expression.Call(
                   powMethod,
                   Expression.Convert(p1, typeof(double)),
                   Expression.Convert(p2, typeof(double)),
               ),
               typeof(T)
           ),
           p1,
           p2
       );
       Pow = powLambda.Compile();
   }
}

// and then in your class

T a, b;
var sum = Calculator<T>.Add(a, b);
var pow = Calculator<T>.Pow(a, b);
like image 42
ChaseMedallion Avatar answered Oct 05 '22 02:10

ChaseMedallion