Generic Constraint to Match Numeric Types

Generic constraint to match numeric types

In this case you want to constrain your generic to the IComparable interface, which gives you access to the CompareTo method, since this interface allows you to answer the question ShouldBeGreaterThan.

Numeric types will implement that interface and the fact that it also works on strings shouldn't bother you that much.

Is there a constraint that restricts my generic method to numeric types?

C# does not support this. Hejlsberg has described the reasons for not implementing the feature in an interview with Bruce Eckel:

And it's not clear that the added complexity is worth the small yield that you get. If something you want to do is not directly supported in the constraint system, you can do it with a factory pattern. You could have a Matrix<T>, for example, and in that Matrix you would like to define a dot product method. That of course that means you ultimately need to understand how to multiply two Ts, but you can't say that as a constraint, at least not if T is int, double, or float. But what you could do is have your Matrix take as an argument a Calculator<T>, and in Calculator<T>, have a method called multiply. You go implement that and you pass it to the Matrix.

However, this leads to fairly convoluted code, where the user has to supply their own Calculator<T> implementation, for each T that they want to use. As long as it doesn’t have to be extensible, i.e. if you just want to support a fixed number of types, such as int and double, you can get away with a relatively simple interface:

var mat = new Matrix<int>(w, h);

(Minimal implementation in a GitHub Gist.)

However, as soon as you want the user to be able to supply their own, custom types, you need to open up this implementation so that the user can supply their own Calculator instances. For instance, to instantiate a matrix that uses a custom decimal floating point implementation, DFP, you’d have to write this code:

var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);

… and implement all the members for DfpCalculator : ICalculator<DFP>.

An alternative, which unfortunately shares the same limitations, is to work with policy classes, as discussed in Sergey Shandar’s answer.

Limit a generic type argument only to be a numeric type

No, there is still no generic type constraint for a "numeric" type.

To get your T a = 0 specifically you can just use T a = default(T). The default of all numeric types is 0.

How to write a generic function that accepts any numerical type?

Go 1.18

With the introduction of type parameters in Go 1.18, this is easier to accomplish.

You can define a function parametrized in T and use an interface constraint to restrict T to numeric types.

func add[T Number](a, b T) T {
return a + b
}

The constraint Number can be defined using golang.org/x/exp/constraints package (still experimental):

import "golang.org/x/exp/constraints"

type Number interface {
constraints.Integer | constraints.Float
}

Where:

  • Number is the union of the type sets of constraints.Integer and constraints.Float
  • constraints.Integer is the set of all signed and unsigned integer types
  • contraints.Float is the set of float types

This will allow you to call add with any two arguments of numeric type. Then in the function body you will be able to use any operation that is supported by all types in the constraint. So in case of numbers, this includes also arithmetic operators. Then declaring similar functions is easy:

func multiply[T Number](a, b T) T {
return a * b
}

Keep in mind that the arguments must have the same type. Regardless of generics, you can't use different types; from the specs Operators:

[...] the operand types must be identical unless the operation involves shifts or untyped constants.

Therefore our generic add and multiply functions are defined with only one type parameter T. This implies that you also can't call the add function with untyped constants whose default types are incompatible:

add(2.5, 2) // won't compile

In this case the compiler will infer the type of T from the first argument 2.5, which defaults to float64, and then won't be able to match the type of 2, which defaults to int.

Full program:

package main

import (
"fmt"

"golang.org/x/exp/constraints"
)

type Number interface {
constraints.Integer | constraints.Float
}

func main() {
a := 1
b := 2

fmt.Println(add(1, 2)) // 3
fmt.Println(add(a, b)) // 3
fmt.Println(add(1.5, 3.2)) // 4.7
// fmt.Println(add(2.5, 2)) // default type int of 2 does not match inferred type float64 for T
}

func add[T Number](a, b T) T {
return a + b
}

Playground: https://go.dev/play/p/rdqi3_-EdHp

Generic type constraint for numerical type only

You can specify type constraints (using both classes and protocols) for a generic class (same syntax applies to functions) using angle brackets:

class Foo<T: Equatable, U: Comparable> { }

To specify more than one requirement on a single type, use a where clause:

class Foo<T: UIViewController where T: UITableViewDataSource, T: UITextFieldDelegate> { }

However, it doesn't look like you can specify optional requirements in a generic parameter clause, so one possible solution is to create a protocol that all the numeric types implement via extensions and then constrain your class on that requirement:

protocol Numeric { }

extension Float: Numeric {}
extension Double: Numeric {}
extension Int: Numeric {}

class NumberCruncher<C1: Numeric> {
func echo(num: C1)-> C1 {
return num
}
}

NumberCruncher<Int>().echo(42)
NumberCruncher<Float>().echo(3.14)

Generic constrain for bitwise operators

Generics are about allowing any Random class that any Programmer on the planet might throw in for T. However the numeric types are actually a very static list. I would never expect a programmer to make his own numeric type. Stuff with a overloaded Operators including binary ones? Maybe rarely.

So this is very much not a generic case. If you only write code for 2 - maybe 3 - types you should cover just about every generic in existence:

  • the highest range integer you have to expect signed Int64 IIRC
  • the highest range floating point you have to expect. IIRC, Decimal*.
  • optionally BigInteger, for when you have to expect really big numbers. However a short look revealed that none of Math class functions support BigInt values. They keep it to Decimal, Double and many smaler built in Numerics. So this case might have been dropped as to rare and to easy to get wrong.

*Correction: While Decimal has the highest amount of digits of precision and bigest size at 64 bit, Double has the bigger range. By an order of Magnitude, that itself has an order of Magnitude.

Generics - where T is a number?

What version of .NET are you using? If you are using .NET 3.5, then I have a generic operators implementation in MiscUtil (free etc).

This has methods like T Add<T>(T x, T y), and other variants for arithmetic on different types (like DateTime + TimeSpan).

Additionally, this works for all the inbuilt, lifted and bespoke operators, and caches the delegate for performance.

Some additional background on why this is tricky is here.

You may also want to know that dynamic (4.0) sort-of solves this issue indirectly too - i.e.

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect

Re the comment about < / > - you don't actually need operators for this; you just need:

T x = ..., T y = ...
int c = Comparer<T>.Default.Compare(x,y);
if(c < 0) {
// x < y
} else if (c > 0) {
// x > y
}

Any elegant way to operate with generic types?

If Eric ever reads this,

If you want something brought to my attention, try the contact link on my blog. Or put my full name in the text of the question so that me searching for myself will find me.

does this feature (constraining generic types by defined operators) ever come up in hypothetical future versions of C# design meetings and has it ever been close to making it into the language?

Indeed, this is a frequently requested feature. We've been getting requests for this sort of thing since C# 1.0.

The feature would requires support from the CLR team, not just the language -- it is the sort of feature that we would want to integrate into all our languages, which increases the cost.

The CLR team has expressed interest in features like this, but they also have a lot of competing features that they could be doing, and limited time and effort to implement those features.

There are numerous ways such a feature could be implemented. For example, we could add the ability to specify static methods in interfaces:

interface IAddable<T>
{
static T operator+(T x, T y);
}

and then

static T Sum<T>(IEnumerable<T> seq) where T : IAddable<T>
{
T sum = default(T);
foreach(T item in seq) sum = sum + item;
return sum;
}

The idea would be that the interface means "a type that implements this interface must have the given static methods". We'd then make int automatically implement IAddable<int>, and so on.

How do do so efficiently in a world with runtime-generated generic code is an open question.

I hasten to add that this is just a sketch of an idea. There are many ways to implement this sort of feature. The "statics in interfaces" idea is one that has broader usage than just mathematics, and that's attractive to us. If we're going to go to the huge expense of this sort of feature, it would be nice to have a really general, powerful feature rather than one narrowly focussed on math.

On the other hand, the perfect is the enemy of the good; it might be better to just concentrate on the math problem and not go for a more expensive general solution.

It's an ongoing debate. It is definitely on everyone's radar screen, but I would not expect it any time soon. The language designers are all heads-down working on going through the feedback from the async CTP.

As always, Eric's musings about hypothetical future language features of hypothetical unannounced future products are for entertainment purposes only.

Generics that restricts the types to Int, Double, long

This is not possible currently.

Also, see this question for a possible workaround



Related Topics



Leave a reply



Submit