explicit cast between generic types

J

Jon

Hi,

When I try to compile the following generic class, the compiler gives
me many errors "Cannot conver type '...' to '...'" on the lines
indicated. Besides, the C# compiler gives me errors even if I don't
declare and instantiate C_Filter_MA<Ti,Ts> in my program.
Ti and Ts should be one of {Byte, UInt16, UInt32, UInt64, SByte,
Int16, Int32, Int64, double}.

Any clue on how to be able to compile this class?
Thank you very much.

// ==============================================================
public class C_Filter_MA<Ti,Ts>
{
// ............................................................
protected Queue<Ti> Ti_queue; // Queue (FIFO) that stores the
items.
protected Ts Ts_sum; // Sum of the values stored in the
queue.
...
// ............................................................
...
// ............................................................
public void Clear()
{
...
Ts_sum =(Ts)0; // <-- Compile error.
...
} // Clear
// ............................................................
public Ti InOutData(Ti Ti_in)
{
...
Ts_sum -=(Ts)Ti_queue.Dequeue(); // <-- Compile error.
...
Ts_sum +=(Ts)Ti_in; // <-- Compile error.
...
} // InOutData
// ............................................................
} // C_Filter_MA
// ==============================================================
 
N

Nicholas Paldino [.NET/C# MVP]

Jon,

Why do you have two types to begin with? How is it that the sum of
types Ti equals a ^NEW^ type Ts? Why wouldn't you just need one type, Ti?

As for the Clear method, you can fix this by using default:

public void Clear()
{
// This should probably be Ti, the same could be applied.
Ts_sum = default(Ti);
} // Clear

If Ti is a blittable type, then an instance of that type will be created
with the data set to 0 for all bits in the type. In the case of int,
double, etc, etc, that means values of 0.

Changing to Ti will fix the Dequeue issue as well.

As for adding, you aren't going to be able to do much with that, since
the constraint system doesn't tell the compiler anything about operations on
a type. Because of this, you will need another class that handles this.
First, you have to define an interface like this:

interface ISimpleMath<T>
{
T Add(T value1, T value2);
T Subtract(T value1, T value2);
}

Then, you implement it for each of the types you want to use, like so:

class IntSimpleMath: ISimpleMath<int>
{
public int Add(int value1, int value2)
{
// Return the sum.
return value1 + value2;
}

public int Subtract(int value1, int value2)
{
// Return the difference.
return value1 - value2;
}
}

Finally, you would add a second type to your generic type which would
indicate the type used to perform the adding:

public class C_Filter_MA<Ti, TSimpleMath> where TSimpleMath:
ISimpleMath<Ti>, new()
{
// Assuming that the operations on the implementation of ISimpleMath are
thread-safe (and
// they should be, since you are passing all operands into the method
and not storing
// shared state), you can store one instance and use that across all
instances of the type.
private static readonly SimpleMathImplementation = new TSimpleMath();

// ............................................................
protected Queue<Ti> Ti_queue; // Queue (FIFO) that stores the items.

// Changed to be of type Ti.
protected Ti Ts_sum; // Sum of the values stored in the queue.

public void Clear()
{
Ts_sum = default(Ti)
} // Clear

public Ti InOutData(Ti Ti_in)
{
// Get the item.
Ti item = Ti_queue.Dequeue();

// Subtract from the sum.
Ts_sum = SimpleMathImplementation.Subtract(Ts_sum, item);

// Add the item passed in.
Ts_sum = SimpleMathImplementation.Add(Ts_sum, Ti_in);
} // InOutData
} // C_Filter_MA
 
M

Marc Gravell

Ts_sum -=(Ts)Ti_queue.Dequeue(); // <-- Compile error.
Ts_sum +=(Ts)Ti_in; // <-- Compile error.

For info, I have a working generic math implementation as an in-
progress side project. It includes standard operators and conversion;
it runtime (not compile-time) validated but is very fast.

I posted some of it on this forum previously, but let me know if this
is of interest.

Marc
 
J

Jon

Jon,

Why do you have two types to begin with? How is it that the sum of
types Ti equals a ^NEW^ type Ts? Why wouldn't you just need one type, Ti?

That was to minimize wasted memory. If each item needs just one byte,
but I need a long moving average filter and therefore many items, I
need the sum to be larger than one byte. I didn't explain it, but the
function "Ti InOutData(Ti Ti_in)" essentially returns (Ti)(Ts_sum/N),
where N is the number of items in the queue. I don't compute that with
a division. I do it by shifting Ts_sum log2(N) times to the right,
because previously I have restricted N to be a power of 2. Anyway, the
point is that the worst case sum needs more bits to be represented
than each of the items.

Example: If I need a 32768-tap moving average filter, and my input
data is 8 bits wide, an instance C_Filter_MA<Byte,UInt32> would be
optimum in terms of memory utilization (which may be critical in
implementations for the .NET Compact Framework). An instance
C_Filter_MA<UInt32,UInt32> would waste 32768*(4-1)=98304 bytes and an
As for the Clear method, you can fix this by using default:

public void Clear()
{
// This should probably be Ti, the same could be applied.
Ts_sum = default(Ti);
} // Clear

If Ti is a blittable type, then an instance of that type will be created
with the data set to 0 for all bits in the type. In the case of int,
double, etc, etc, that means values of 0.

That compiles ok. Thank you.
Changing to Ti will fix the Dequeue issue as well.

As for adding, you aren't going to be able to do much with that, since
the constraint system doesn't tell the compiler anything about operations on
a type. Because of this, you will need another class that handles this.
First, you have to define an interface like this:

interface ISimpleMath<T>
{
T Add(T value1, T value2);
T Subtract(T value1, T value2);
}

Then, you implement it for each of the types you want to use, like so:

class IntSimpleMath: ISimpleMath<int>
{
public int Add(int value1, int value2)
{
// Return the sum.
return value1 + value2;
}

public int Subtract(int value1, int value2)
{
// Return the difference.
return value1 - value2;
}
}

I had read about type constraints and I had tried specifying that Ti
and Ts implement the same interfaces as, for instance, Int32 (which
implements, according to the documentation: {IComparable,
IFormattable, IConvertible, IComparable<int>, IEquatable<int>}).

So, I tried, for instance:

// ==============================
public class C_Filter_MA<Ti,Ts>
where Ti : IComparable, IFormattable, IConvertible, IComparable<Ti>,
IEquatable<Ti>
where Ts : IComparable, IFormattable, IConvertible, IComparable<Ts>,
IEquatable<Ts>
{
...
}
// ==============================

but that didn't work either. It still doesn't know how to add or
subtract. My quesion is: Isn't there any existing interface, which
would be common to the integral and floating point types, which
exposes methods so common as adding, subtracting, multiplying, etc?
Because that would be the interface that I need. I can write the
custom interface that you propose, and then implement it for each of
{Byte, UInt16, UInt32...}, but then I would have the feeling that I am
writing something that many other people has done many times before
me. For instance, your class "IntSimpleMath". Do I really need to
teach again the compiler how to add two integers? Shouldn't it know
already what to do if it finds "some_int+some_int" or
"some_int+=some_int"? I'm knot questioning or criticising what you
say. On the contrary. I'm very grateful to you for your time. I'm just
surprised.

Finally, you would add a second type to your generic type which would
indicate the type used to perform the adding:

public class C_Filter_MA<Ti, TSimpleMath> where TSimpleMath:
ISimpleMath<Ti>, new()
{
// Assuming that the operations on the implementation of ISimpleMath are
thread-safe (and
// they should be, since you are passing all operands into the method
and not storing
// shared state), you can store one instance and use that across all
instances of the type.
private static readonly SimpleMathImplementation = new TSimpleMath();

// ............................................................
protected Queue<Ti> Ti_queue; // Queue (FIFO) that stores the items.

// Changed to be of type Ti.
protected Ti Ts_sum; // Sum of the values stored in the queue.

public void Clear()
{
Ts_sum = default(Ti)
} // Clear

public Ti InOutData(Ti Ti_in)
{
// Get the item.
Ti item = Ti_queue.Dequeue();

// Subtract from the sum.
Ts_sum = SimpleMathImplementation.Subtract(Ts_sum, item);

// Add the item passed in.
Ts_sum = SimpleMathImplementation.Add(Ts_sum, Ti_in);
} // InOutData
} // C_Filter_MA

Well, I'll try defining my custom interface.

Thank you very much.
 
J

Jon

Hi Nicholas,

I tried your custom interface solution. I end up with only 4 compile
erros, in two lines. Search for token "errors" in the source code
shown below. In line

private static readonly Ts_SimpleMathImplementation= new
Ts_SimpleMath();

it is like it doesn't know how to create a new instance of
Ts_SimpleMath. The "new ()" in line

where Ti_SimpleMath : I_SimpleMath<Ti>, new()

presumably means that Ti_SimpleMath must have a public parameterless
constructor. How can I specify the constructor of Ti_SimpleMath ? In
I_SimpleMath<T>? How?

I know that I ended up not using "Ti_SimpleMathImplementation", but I
would keep it just in case. I do use "Ts_SimpleMathImplementation".

Sorry about the long post.

If you know why I'm getting those errors, please let me know.
Thank you.

PS: I'm starting to think about not using generics for this purpose,
and instead define two or three classes like "C_Filter_MA_u08_u32",
"C_Filter_MA_u16_u32" and "C_Filter_MA_double_double", for instance,
because I'll probably end up with fewer lines of code. All this has
really surprised me.


---------------------------------------
using System;
using System.Collections.Generic;

// These "using" are the C# equivalent ones to "typedef" in C++.
using u08 =System.Byte;
using u16 =System.UInt16;
using u32 =System.UInt32;
using u64 =System.UInt64;

using i08 =System.SByte;
using i16 =System.Int16;
using i32 =System.Int32;
using i64 =System.Int64;

namespace N_Filters
{
#region Interfaces and their implementations.
// ===============================================================
// Interfaces cannot contain operators.
public interface I_SimpleMath<T>
{
T Add(T val1, T val2);
T Subtract(T val1, T val2);
T Multiply(T val1, T val2);
T Divide(T val1, T val2);
T ShiftLeft(T val1,u08 shift_amount);
T ShiftRight(T val1,u08 shift_amount);
} // I_SimpleMath
// ===============================================================
public class C_SimpleMath_u08 : I_SimpleMath<u08>
{
public u08 Add(u08 val1, u08 val2)
{
return((u08)(val1+val2));
}
public u08 Subtract(u08 val1, u08 val2)
{
return((u08)(val1-val2));
}
public u08 Multiply(u08 val1, u08 val2)
{
return((u08)(val1*val2));
}
public u08 Divide(u08 val1, u08 val2)
{
return((u08)(val1/val2));
}
public u08 ShiftLeft(u08 val1,u08 shift_amount)
{
return((u08)(val1<<shift_amount));
}
public u08 ShiftRight(u08 val1,u08 shift_amount)
{
return((u08)(val1>>shift_amount));
}
} // C_SimpleMath_u08
// ===============================================================
public class C_SimpleMath_u16 : I_SimpleMath<u16>
{
public u16 Add(u16 val1, u16 val2)
{
return((u16)(val1+val2));
}
public u16 Subtract(u16 val1, u16 val2)
{
return((u16)(val1-val2));
}
public u16 Multiply(u16 val1, u16 val2)
{
return((u16)(val1*val2));
}
public u16 Divide(u16 val1, u16 val2)
{
return((u16)(val1/val2));
}
public u16 ShiftLeft(u16 val1,u08 shift_amount)
{
return((u16)(val1<<shift_amount));
}
public u16 ShiftRight(u16 val1,u08 shift_amount)
{
return((u16)(val1>>shift_amount));
}
} // C_SimpleMath_u16
// ===============================================================
public class C_SimpleMath_u32 : I_SimpleMath<u32>
{
public u32 Add(u32 val1, u32 val2)
{
return((u32)(val1+val2));
}
public u32 Subtract(u32 val1, u32 val2)
{
return((u32)(val1-val2));
}
public u32 Multiply(u32 val1, u32 val2)
{
return((u32)(val1*val2));
}
public u32 Divide(u32 val1, u32 val2)
{
return((u32)(val1/val2));
}
public u32 ShiftLeft(u32 val1,u08 shift_amount)
{
return((u32)(val1<<shift_amount));
}
public u32 ShiftRight(u32 val1,u08 shift_amount)
{
return((u32)(val1>>shift_amount));
}
} // C_SimpleMath_u32
// ===============================================================
public class C_SimpleMath_u64 : I_SimpleMath<u64>
{
public u64 Add(u64 val1, u64 val2)
{
return((u64)(val1+val2));
}
public u64 Subtract(u64 val1, u64 val2)
{
return((u64)(val1-val2));
}
public u64 Multiply(u64 val1, u64 val2)
{
return((u64)(val1*val2));
}
public u64 Divide(u64 val1, u64 val2)
{
return((u64)(val1/val2));
}
public u64 ShiftLeft(u64 val1,u08 shift_amount)
{
return((u64)(val1<<shift_amount));
}
public u64 ShiftRight(u64 val1,u08 shift_amount)
{
return((u64)(val1>>shift_amount));
}
} // C_SimpleMath_u64
// ===============================================================
public class C_SimpleMath_i08 : I_SimpleMath<i08>
{
public i08 Add(i08 val1, i08 val2)
{
return((i08)(val1+val2));
}
public i08 Subtract(i08 val1, i08 val2)
{
return((i08)(val1-val2));
}
public i08 Multiply(i08 val1, i08 val2)
{
return((i08)(val1*val2));
}
public i08 Divide(i08 val1, i08 val2)
{
return((i08)(val1/val2));
}
public i08 ShiftLeft(i08 val1,u08 shift_amount)
{
return((i08)(val1<<shift_amount));
}
public i08 ShiftRight(i08 val1,u08 shift_amount)
{
return((i08)(val1>>shift_amount));
}
} // C_SimpleMath_i08
// ===============================================================
public class C_SimpleMath_i16 : I_SimpleMath<i16>
{
public i16 Add(i16 val1, i16 val2)
{
return((i16)(val1+val2));
}
public i16 Subtract(i16 val1, i16 val2)
{
return((i16)(val1-val2));
}
public i16 Multiply(i16 val1, i16 val2)
{
return((i16)(val1*val2));
}
public i16 Divide(i16 val1, i16 val2)
{
return((i16)(val1/val2));
}
public i16 ShiftLeft(i16 val1,u08 shift_amount)
{
return((i16)(val1<<shift_amount));
}
public i16 ShiftRight(i16 val1,u08 shift_amount)
{
return((i16)(val1>>shift_amount));
}
} // C_SimpleMath_i16
// ===============================================================
public class C_SimpleMath_i32 : I_SimpleMath<i32>
{
public i32 Add(i32 val1, i32 val2)
{
return((i32)(val1+val2));
}
public i32 Subtract(i32 val1, i32 val2)
{
return((i32)(val1-val2));
}
public i32 Multiply(i32 val1, i32 val2)
{
return((i32)(val1*val2));
}
public i32 Divide(i32 val1, i32 val2)
{
return((i32)(val1/val2));
}
public i32 ShiftLeft(i32 val1,u08 shift_amount)
{
return((i32)(val1<<shift_amount));
}
public i32 ShiftRight(i32 val1,u08 shift_amount)
{
return((i32)(val1>>shift_amount));
}
} // C_SimpleMath_i32
// ===============================================================
public class C_SimpleMath_i64 : I_SimpleMath<i64>
{
public i64 Add(i64 val1, i64 val2)
{
return((i64)(val1+val2));
}
public i64 Subtract(i64 val1, i64 val2)
{
return((i64)(val1-val2));
}
public i64 Multiply(i64 val1, i64 val2)
{
return((i64)(val1*val2));
}
public i64 Divide(i64 val1, i64 val2)
{
return((i64)(val1/val2));
}
public i64 ShiftLeft(i64 val1,u08 shift_amount)
{
return((i64)(val1<<shift_amount));
}
public i64 ShiftRight(i64 val1,u08 shift_amount)
{
return((i64)(val1>>shift_amount));
}
} // C_SimpleMath_i64
// ===============================================================
public class C_SimpleMath_double : I_SimpleMath<double>
{
public double Add(double val1, double val2)
{
return((double)(val1+val2));
}
public double Subtract(double val1, double val2)
{
return((double)(val1-val2));
}
public double Multiply(double val1, double val2)
{
return((double)(val1*val2));
}
public double Divide(double val1, double val2)
{
return((double)(val1/val2));
}
public double ShiftLeft(double val1,u08 shift_amount)
{
return(val1*Math.Pow(2.0,(double)shift_amount));
}
public double ShiftRight(double val1,u08 shift_amount)
{
return(val1*Math.Pow(2.0,(double)shift_amount*-1.0));
}
} // C_SimpleMath_double
#endregion
// ===============================================================
// Moving average filter.
// Ti = T_item {u08,u16,u32,u64,i08,i16,i32,i64,double}.
// Ts = T_sum {u08,u16,u32,u64,i08,i16,i32,i64,double}.

public class C_Filter_MA<Ti,Ti_SimpleMath,Ts,Ts_SimpleMath>
where Ti : IComparable, IFormattable, IConvertible,
IComparable<Ti>, IEquatable<Ti>
where Ti_SimpleMath : I_SimpleMath<Ti>, new()
where Ts : IComparable, IFormattable, IConvertible,
IComparable<Ts>, IEquatable<Ts>
where Ts_SimpleMath : I_SimpleMath<Ts>, new()
{
// .............................................................
private static readonly Ti_SimpleMathImplementation= new
Ti_SimpleMath(); // <-- Compile errors "Invalid token '=' in class,
struct, or interface member declaration" and "Class, struct, or
interface method must have a return type".
private static readonly Ts_SimpleMathImplementation= new
Ts_SimpleMath(); // <-- Compile errors "Invalid token '=' in class,
struct, or interface member declaration" and "Class, struct, or
interface method must have a return type".
// .............................................................
protected Queue<Ti> Ti_queue; // Queue (FIFO) that
stores the items.
public int Ti_queue_capacity; // Queue does not
have any member field called Capacity.
protected Ts Ts_sum; // Sum of the values
stored in the queue.
protected u08 u08_log2ofsize; // Log2(<size of
fifo>). So, size of fifo will always be a power of 2.
// .............................................................
public C_Filter_MA(u08 u08_log2ofsize)
{
this.u08_log2ofsize=u08_log2ofsize;
Ti_queue_capacity=1<<u08_log2ofsize;
Ti_queue=new Queue<Ti>(Ti_queue_capacity);
Clear();
} // C_Filter_MA
// .............................................................
public void Clear()
{
Ti_queue.Clear();
Ts_sum=default(Ts);
} // Clear
// .............................................................
public void Init(Ti Ti_initial)
{
Ti_queue.Clear();

Ts_sum=(Ts)Convert.ChangeType(Ti_initial.ToInt32()*Ti_queue_capacity,typeof(Ts));
if (Ti_initial.Equals(default(Ti))) return;
for (int i=0;i<Ti_queue_capacity;i++)
{
Ti_queue.Enqueue(Ti_initial);
} // for
} // Init
// .............................................................
public Ti InOutData(Ti Ti_in)
{
if (Ti_queue.Count>=Ti_queue_capacity)
{

Ts_sum=Ts_SimpleMathImplementation.Subtract(Ts_sum,(Ts)Convert.ChangeType(Ti_queue.Dequeue(),typeof(Ts)));
} // if
Ti_queue.Enqueue(Ti_in);

Ts_sum=Ts_SimpleMathImplementation.Add(Ts_sum,(Ts)Convert.ChangeType(Ti_in,typeof(Ts)));
return(
(Ti)Convert.ChangeType(Ts_SimpleMathImplementation.ShiftRight(Ts_sum,u08_log2ofsize),typeof(Ti))
);
} // InOutData
// .............................................................
} // C_Filter_MA
// ===============================================================
} // N_Filters
 
J

Jon

Ts_sum -=(Ts)Ti_queue.Dequeue(); // <-- Compile error.
Ts_sum +=(Ts)Ti_in; // <-- Compile error.

For info, I have a working generic math implementation as an in-
progress side project. It includes standard operators and conversion;
it runtime (not compile-time) validated but is very fast.

I posted some of it on this forum previously, but let me know if this
is of interest.

Marc

Even though I prefer compile-time validation, I would be glad to take
a look at your implementation. If you have a link, please let me know.
Otherwise, please write me at "cucafera (at) telefonica (dot) com"

Thank you.
 
M

Marc Gravell

I have e-mailed you a sample, but I've also just noticed that you
mention CF. Unfortunately, the code doesn't compile in CF 3.5, as it
uses some framework classes that aren't supported on that platform. It
may be possible to re-work the concept using Delegate.CreateDelegate
and the various operators, but there are a lot of special cases where
the operator is supplied by the compiler (not the Type) which would
need surrogate methods (which is partly what I was hoping to avoid).

Marc
 
B

Ben Voigt [C++ MVP]

but that didn't work either. It still doesn't know how to add or
subtract. My quesion is: Isn't there any existing interface, which
would be common to the integral and floating point types, which
exposes methods so common as adding, subtracting, multiplying, etc?

You've just nailed the biggest complaint about generics.
 
B

Ben Voigt [C++ MVP]

Jon said:
Hi Nicholas,

I tried your custom interface solution. I end up with only 4 compile
erros, in two lines. Search for token "errors" in the source code
shown below. In line

private static readonly Ts_SimpleMathImplementation= new
Ts_SimpleMath();

it is like it doesn't know how to create a new instance of
Ts_SimpleMath. The "new ()" in line

where Ti_SimpleMath : I_SimpleMath<Ti>, new()

presumably means that Ti_SimpleMath must have a public parameterless
constructor. How can I specify the constructor of Ti_SimpleMath ? In
I_SimpleMath<T>? How?

I think you would be better off defining a class

interface IAccumulator<TSum, TAddend>
{
TSum Value { get; }
void Increment(TAddend addend);
}

and implementing it for (Int32, Byte) for example.

Then use that from your generic algorithm to get around the lack of math
operators,

Finally, I guess you may end up needing

interface IMultiplyAdder<TSum, TAddend, TGain>
{
TSum Value { get; }
void MulAdd(TGain coefficient, TAddend addend);
}

class MultiplyAdderIntByteByte : IMultiplyAdder<int, byte, byte>
{
private int internalValue;
int Value { get { return internalValue; } }
void MulAdd(byte coefficient, byte addend) { internalValue +=
coefficient * addend; }
}

This will satisfy a constraint
where TMultiplyAdder : IMultiplyAdder<Ts, Ti, Ti>, new()
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top