Operator '+' cannot be applied to types 'T' and 'T'. in TypeScript
Asked Answered
S

6

10

I am very new to TypeScript. My TypeScript version is 3.7.5.

IMHO, it should be very easy, but I don't know why it does not work.

function add<T> (a:T, b:T):T  {
    return a + b ;
}

console.log(add (5, 6));

I get the error:

Operator '+' cannot be applied to types 'T' and 'T'.

I used this one also:

function add<T extends string | number > (a:T, b:T):T

The same error is there. If I can not use + for this generic, why should I use generics?

Septarium answered 18/1, 2020 at 17:11 Comment(3)
The question is not why can't you use + for generics, the question is why would you perform arithmetics on anything but a number?Dardan
@AliHabibzadeh arithmetic or string concatenation. You could easily have an operation similar to ["a", "b", "c"].reduce(add) where you take two strings at a time and pass them through a binary function.Fotheringhay
You never need to add or subtract strings. You need to use string interpolation, .concat, etc.. Math operations are not for strings. If you use them for strings, you are coding wrong.Dardan
V
17

Generics are not the right approach here. You cannot apply the + operator to an unconstrained T (why should this work?).

function add<T extends string | number > (a:T, b:T):T won't work either, because TypeScript requires at least one operand to be string, which is not the case here. E.g., what about this constellation:

const sn1 = 3 as number | string
const sn2 = "dslf" as number | string
add(sn1, sn2) // Both types have type number | string, sh*t...

The + operator cannot be overloaded, but we can still leverage function overloads in TypeScript:

function add(a: string, b: string): string
function add(a: number, b: number): number
function add(a: any, b: any) {
    return a + b;
}

add(1, 2) // Number
add("foo", "bar") // String
Verdict answered 18/1, 2020 at 18:40 Comment(5)
why you think JS allows overload?Alodi
I meant TS function overloads, gonna update.Verdict
why should this work? - can you explain why it should not?Detumescence
@SET the compiler doesn't know anything about T, which in this case gets unknown as upper-bound type. So from TS perspective, this is likely to be an error.Verdict
And if they use ES6? Arrow function?Femme
A
8

These are quite common solutions:

Union addition

function add<T extends string | number>(a: T, b: T): T extends string ? string : number  {
  return  <any>a + <any>b; // Cast to any as unions cannot be added, still have proper typings applied
}

const res1 = add(5, 6) // number
const res2 = add('a', 'b') // string
const res3 = add(5, 'b') // Argument of type '"b"' is not assignable to parameter of type '5'.

Playground



TypeScript function overloading

function add(a: string, b: string): string
function add(a: number, b: number): number
function add(a: any, b: any): string | number {
  return a + b;
}

const res1 = add(1, 2); // Number
const res2 = add('a', 'b'); // String
const res3 = add(1, 'b'); // Overload 1 of 2, '(a: string, b: string): string', gave the following error.

Playground

Alodi answered 18/1, 2020 at 18:47 Comment(2)
indeed, why not to solve any TS-related problem with anyDetumescence
This answer is currently under discussion on the meta.Uptotheminute
H
4

It looks like it is necessary to set constraints like this:

add<T extends number> (a:T, b:T):number {
    return a + b;
}

or T of string:

add<T extends string> (a:T, b:T):string {
    return a + b;
}

This version is not eligible, as whether you get concatenation or addition is not predictable.

add<T extends string | number > (a:T, b:T):T {

}
Hubris answered 18/1, 2020 at 17:18 Comment(6)
A more general approach might be to create an interface with an add: (other: T) => T; member and use that as the constraint. Would be nice if we could overload operators!!Showers
if I define both of them I get this: Duplicate function implementation. if I want to define only one why should I go for T I can simply define with number ``` function add (a:number, b: number):number { return a + b ; } console.log(add (5,6)); ```Septarium
@HesamA yeah, you are right, you can simple define as add (a:number, b: number):number { return a + b ; }Hubris
"This version is not eligible as whether you get concatenation or addition is not predictable." Should it matter? I'm quite interested in why this version doesn't work, the constraints seem satisfied, and if the signature is satisfied then weather its concat or addition is an implementation detail, no different than T extending IHasAdd and the non-operator add operation for number doing addition and for string doing concat.Vinna
@MâttFrëëman T extends string | number then T can be string | number means one value of type T can be string and another value of type T can be a number.Hubris
Shouldn't it be using the same type T for both? How else would you constrain two types to be the same using generics? This does seem like a compiler bug IMO. In fact, the compiler definitely knows / expects them to be the same, which is why it refuses to compile add('s', 5)Fossette
R
0

There are great answers here. but wanted to show what would be the practical way this can happen and TS would not able to infer the types.

function sum<T extends number|string>(a:T,b:T){
   return a+b;
} 

function getNumberOrString(){
  if(Math.random()<0.5){
    return "5";
  }else{
    return 6;
 }
}


let k =getNumberOrString();
let k1 =getNumberOrString();

//type safety issue as k can be number and k1 can be string
console.log(sum(k,k1)); 
Rubadub answered 21/1, 2024 at 15:36 Comment(0)
M
0

The Microsoft TypeScript training shows the way to solve the problem.

Please check the section "Use the methods and properties of a generic type" in the module "Define generics in TypeScript"

type ValidTypes = string | number;
function add<T extends ValidTypes> (a:T, b:T)  // Return type is inferred 
{ 
    if (typeof a === 'number' && typeof b === 'number') {      
        return a + b; 
    } else if (typeof a === 'string' && typeof b === 'string') {   
        return a + b; 
    } else {
        return undefined;
    }
}

console.log(add<number>(5, 6));      // 11
console.log(add<string>("5", "6"));  // "56" 
console.log(add (5, "6"));           // undefined

TS Playground

And if you want to mix the parameter types

type ValidTypes = string | number;
function add<T extends ValidTypes, U extends ValidTypes> (a:T, b:U)  // Return type is inferred 
{ 
    if (typeof a === 'number' && typeof b === 'number') {      
        return a + b; 
    } else if (typeof a === 'string' && typeof b === 'string') {   
        return a + b; 
    } else if (typeof a === 'number' && typeof b === 'string') {   
        return a + b; 
    } else if (typeof a === 'string' && typeof b === 'number') {   
        return a + b;         
    } else {
        return undefined;
    }
}

console.log(add<number, number>(5, 6));     // 11
console.log(add<string,string>("5", "6"));  // "56"
console.log(add<string,number>("5", 6));    // "56"
console.log(add<number,string>(5, "6"));    // "56"
console.log(add<number,number>(5, "6")); // Error 2345: Argument of type 'string' is not assignable to parameter of type 'number'

console.log(add (5, 6));    // 11
console.log(add("5", "6")); // "56"
console.log(add (5, "6"));  // "56"
console.log(add ("5", 6));  // "56"
console.log(add (5, null)); // undefined

TS Playground

Menu answered 19/8, 2024 at 9:18 Comment(0)
M
-2

specify the type of T, and then use T as a variable to limit the types of x and y

function addString<T extends string>(x: T, y: T): string {
  return x + y;
}

function addNum<T extends number>(x: T, y: T): number {
  let a = x * y;
  let b = x - y;
  let z = x++;
  return x + y;
}

addString("a", "b");
addNum(1, 2);
Midday answered 14/12, 2020 at 2:9 Comment(2)
Thanks for your contribution. Consider adding an explanation to your code, since that will be more helpful.Handed
An explanation would be in order. You can edit your answer (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).Azpurua

© 2022 - 2025 — McMap. All rights reserved.