# Implement type constraints like in C #

In the development of C # port of AtCoderLibrary `Class <arbitrary numeric type I have the intention of realizing a type like> `, and I will describe the result of the examination.

As an example for explanation, consider the following program that calculates the greatest common divisor.

``````class Program
{
static void Main()
{
System.Console.WriteLine(new Gcder(30030).Calc(42)); // → 42
System.Console.WriteLine(new Gcder(120).Calc(119)); // → 1
}
}
class Gcder
{
static int Gcd(int a, int b) => b > a ? Gcd(b, a) : (b == 0 ? a : Gcd(b, a % b));
public int a;
public Gcder(int a) { this.a = a; }
public int Calc(int b) => Gcd(a, b);
}
``````

## Problem: Numerical calculation cannot be made Generic

If you try to make this generic, it will not be straightforward.

This is because `b> a`,` b == 0`, ʻa% b`, etc. are undefined.

``````class Program
{
static void Main()
{
System.Console.WriteLine(new Gcder<int>(30030).Calc(42));
System.Console.WriteLine(new Gcder<ulong>(30030).Calc(42));
}
}
class Gcder<T> where T : struct
{
static T Gcd(T a, T b) => b > a ? Gcd(b, a) : (b == 0 ? a : Gcd(b, a % b)); //This line cannot be compiled
public T a;
public Gcder(T a) { this.a = a; }
public T Calc(T b) => Gcd(a, b);
}
``````

### Realization in C ++

In C ++, it can be expressed using the conditional expression of template.

``````#include <type_traits>
#include <iostream>

template <class T>
auto gcd(T a, T b) -> typename std::enable_if<std::is_integral<T>::value, T>::type
{
return b > a ? gcd(b, a) : (b == 0 ? a : gcd(b, a % b));
}

int main()
{
std::cout << gcd(120, 96) << std::endl;
std::cout << gcd(120ULL, 96ULL) << std::endl;
// std::cout << gcd("120", 96) << std::endl; // Compilation error
}
``````

## Solution: Interface for numerical calculation

### interface definition

It is good to prepare an interface for numerical calculation.

``````public interface INumOperator<T> : IEqualityComparer<T>, IComparer<T> where T : struct
{
T MinValue { get; }
T MaxValue { get; }
T Subtract(T x, T y);
T Multiply(T x, T y);
T Divide(T x, T y);
T Modulo(T x, T y);
bool GreaterThan(T x, T y);
bool GreaterThanOrEqual(T x, T y);
bool LessThan(T x, T y);
bool LessThanOrEqual(T x, T y);
}
public readonly struct IntOperator : INumOperator<int>
{
public int MinValue => int.MinValue;
public int MaxValue => int.MaxValue;
public int Add(int x, int y) => x + y;
public int Subtract(int x, int y) => x - y;
public int Multiply(int x, int y) => x * y;
public int Divide(int x, int y) => x / y;
public int Modulo(int x, int y) => x % y;
public bool GreaterThan(int x, int y) => x > y;
public bool GreaterThanOrEqual(int x, int y) => x >= y;
public bool LessThan(int x, int y) => x < y;
public bool LessThanOrEqual(int x, int y) => x <= y;
public int Compare(int x, int y) => x.CompareTo(y);
public bool Equals(int x, int y) => x == y;
public int GetHashCode(int obj) => obj.GetHashCode();
}
// LongOperator, UIntOperator,ULongOperator etc. are defined in the same way
``````

The point here is that ʻIntOperator` is` struct`. Details will be described later.

### Completed form

``````class Program
{
static void Main()
{
System.Console.WriteLine(new Gcder<int, IntOperator>(30030).Calc(42));
System.Console.WriteLine(new Gcder<ulong, ULongOperator>(30030).Calc(42));
System.Console.WriteLine(new Gcder<int, IntOperator>(120).Calc(119));
System.Console.WriteLine(new Gcder<ulong, ULongOperator>(120).Calc(119));
}
}
class Gcder<T, TOp> where T : struct where TOp : struct, INumOperator<T>
{
static T Gcd(T a, T b) => op.GreaterThan(b, a) ? Gcd(b, a) : (op.Equals(b, default) ? a : Gcd(b, op.Modulo(a, b)));
public T a;
public Gcder(T a) { this.a = a; }
public T Calc(T b) => Gcd(a, b);
}
``````

Now, ʻint` is also genericized for ʻulong`.

## Consideration

### Reasons to implement a math interface in a struct

There are two main reasons to implement a math interface in a struct.

–struct is non-nullable
–When a struct enters a type argument, the type is expanded and fast

The former is easy because you can just use `static readonly TOp op;` without initializing it.

The latter is important.

If the arithmetic type is class, calls like ʻop.GreaterThan (b, a) ```are virtualized and will never be JIT optimized. On the other hand, in the case of struct, ʻop.GreaterThan (b, a) ```can be expanded by JIT optimization. Therefore, it will operate at high speed.

Benchmarks

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.1082 (1909/November2018Update/19H2)
Intel Core i7-4790 CPU 3.60GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.401
[Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT
DefaultJob : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
DirectInt 1.173 s 0.0046 s 0.0038 s - - - 304 B
GenericInt 1.292 s 0.0054 s 0.0048 s - - - 2112 B
GenericClassInt 2.361 s 0.0463 s 0.0747 s - - - 304 B

Code</summary><div>

``````class Program
{
static void Main()
{
}
}
class GcderInt
{
static int Gcd(int a, int b) => b > a ? Gcd(b, a) : b == default ? a : Gcd(b, a % b);
public int a;
public GcderInt(int a) { this.a = a; }
public int Calc(int b) => Gcd(a, b);
}
class Gcder<T, TOp> where T : struct where TOp : INumOperator<T>, new()
{
static readonly TOp op = new TOp();
static T Gcd(T a, T b) => op.GreaterThan(b, a) ? Gcd(b, a) : (op.Equals(b, default) ? a : Gcd(b, op.Modulo(a, b)));
public T a;
public Gcder(T a) { this.a = a; }
public T Calc(T b) => Gcd(a, b);
}
public class ClassIntOperator : INumOperator<int>
{
public int MinValue => int.MinValue;
public int MaxValue => int.MaxValue;
public int Add(int x, int y) => x + y;
public int Subtract(int x, int y) => x - y;
public int Multiply(int x, int y) => x * y;
public int Divide(int x, int y) => x / y;
public int Modulo(int x, int y) => x % y;
public bool GreaterThan(int x, int y) => x > y;
public bool GreaterThanOrEqual(int x, int y) => x >= y;
public bool LessThan(int x, int y) => x < y;
public bool LessThanOrEqual(int x, int y) => x <= y;
public int Compare(int x, int y) => x.CompareTo(y);
public bool Equals(int x, int y) => x == y;
public int GetHashCode(int obj) => obj.GetHashCode();
}
public class AclBench
{
const int N = 10_000_000;

[Benchmark]
public long DirectInt()
{
var rnd = new System.Random(42);
long sum = 0;
var gcder = new GcderInt(2 * 3 * 5 * 7 * 9 * 11 * 13 * 17 * 19 * 23);
for (int i = 0; i < N; i++)
{
sum += gcder.Calc(rnd.Next());
}
return sum;
}

[Benchmark]
public long GenericInt()
{
var rnd = new System.Random(42);
long sum = 0;
var gcder = new Gcder<int, IntOperator>(2 * 3 * 5 * 7 * 9 * 11 * 13 * 17 * 19 * 23);
for (int i = 0; i < N; i++)
{
sum += gcder.Calc(rnd.Next());
}
return sum;
}

[Benchmark]
public long GenericClassInt()
{
var rnd = new System.Random(42);
long sum = 0;
var gcder = new Gcder<int, ClassIntOperator>(2 * 3 * 5 * 7 * 9 * 11 * 13 * 17 * 19 * 23);
for (int i = 0; i < N; i++)
{
sum += gcder.Calc(rnd.Next());
}
return sum;
}
}
public interface INumOperator<T> : System.Collections.Generic.IEqualityComparer<T>, System.Collections.Generic.IComparer<T> where T : struct
{
T MinValue { get; }
T MaxValue { get; }
T Subtract(T x, T y);
T Multiply(T x, T y);
T Divide(T x, T y);
T Modulo(T x, T y);
bool GreaterThan(T x, T y);
bool GreaterThanOrEqual(T x, T y);
bool LessThan(T x, T y);
bool LessThanOrEqual(T x, T y);
}
public readonly struct IntOperator : INumOperator<int>
{
public int MinValue => int.MinValue;
public int MaxValue => int.MaxValue;
public int Add(int x, int y) => x + y;
public int Subtract(int x, int y) => x - y;
public int Multiply(int x, int y) => x * y;
public int Divide(int x, int y) => x / y;
public int Modulo(int x, int y) => x % y;
public bool GreaterThan(int x, int y) => x > y;
public bool GreaterThanOrEqual(int x, int y) => x >= y;
public bool LessThan(int x, int y) => x < y;
public bool LessThanOrEqual(int x, int y) => x <= y;
public int Compare(int x, int y) => x.CompareTo(y);
public bool Equals(int x, int y) => x == y;
public int GetHashCode(int obj) => obj.GetHashCode();
}
``````

</div></details>

## Digression

It seems that the function called Type Classes, which is targeted at C # 10.0, is exactly like this.

https://github.com/dotnet/csharplang/issues/110

Tags:

Updated: