A while ago on Facebook (it’s a private group, so you cannot see the posts unless you both have a Facebook account and are member of the group), [Archive.is] Niels Tjørnhøj-Thomsen (coming from a C++ templates background) asked why the below method would throw a E2015 Operator not applicable to this operand type
in the complex expression:
function TAxis<t>.Calc(const AScalar: T): single; begin Result := fStart + ( ( ( AScalar - fMin ) / fRange ) * fExtent ); end;
The type itself was very simple:
TAxis<T> = record fMin, fMax, fRange: T; fStart, fEnd, fExtent: single; function Calc( const AScalar: T ): single; end;
He used these small example specialisations that put me on the wrong foot, as the order was TDateTime
followed by single
:
var rXAxis: TAxis<TDateTime>; rYAxis: TAxis<single>;
So at first I thought this might be caused by TDateTime
to be defined in the System
unit as a typed type:
type TDateTime = type Double;
It wasn’t.
Splitting the code in 4 lines with assignments of single expression operations would make the error appear in all expressions.
Casting parts of the expression to simple
would not help either.
A small test program [Archive.is] might put you, like me, on the wrong foot because the specialisation is in the same source file as the generic type:
program DelphiMathAndGenerics; type TAxis<T> = record fMin, fMax, fRange: T; fStart, fEnd, fExtent: single; function CalcCasted( const AScalar: T ): single; function CalcPlain( const AScalar: T ): single; end; function TAxis<T>.CalcCasted(const AScalar: T): single; var Offset: single; NormalisedOffset: single; ScaledOffset: single; begin // First 2 lines give the same error: E2089 Invalid typecast Offset := single(AScalar) - fMin; NormalisedOffset := Offset / single(fRange); ScaledOffset := NormalisedOffset * fExtent; Result := fStart + ScaledOffset; end; function TAxis<T>.CalcPlain(const AScalar: T): single; var Offset: T; NormalisedOffset: T; ScaledOffset: T; begin // All 4 lines give the same error: E2015 Operator not applicable to this operand type Offset := AScalar - fMin; NormalisedOffset := Offset / fRange; ScaledOffset := NormalisedOffset * fExtent; Result := fStart + ScaledOffset; end; var rXAxis: TAxis<TDateTime>; rYAxis: TAxis<single>; begin end.
Splitting this in two files [Archive.is], a AxisUnit
unit having only the TAxis<T>
type, and a main program (even without having the specialisations) shows that even the unit itself would not compile.
This shows a major difference between Delphi (and similar C#) generics and C++ templates:
- generics are compiled and fully verified at the generic stage
- templates are pre-processed, then finally verified at specialisation stage
A solution would be that Delphi could constraint the generic type T
into something like float
or ordinal
so the compiler would know that more operators are allowed in the code. But alas, Delphi – like C# – has a very limited number of constraints (C# only would allow a constraint for enumerations in version 7.3): Delphi Constraints in Generics – RAD Studio XE documentation wiki.
This StackOverflow question is very similar, and has the same answer (generics in Delphi work differently than templates in C++): [Source] templates – Arithmetic operations with generic types in Delphi – Stack Overflow
I’m new in Delphi. For a project required by my company, I need to translate some code from our existing C++ classes to Delphi. Some of these classes are templates, such …
Workaround: use the TValue.From<T>() function
There is a workaround though, but it is slow, as you need to convert from the generic T
type to the actual (in this case floating point) type you can apply the operators on.
This is possible with the (Delphi 2010 introduced) TValue.From<T>() method which returns a TValue record. That TValue record has instance methods like AsExtended to extract or convert the embedded value as a specific type.
Initially, [Wayback] Delphi 2010 Rtti.TValue
documentation had the From
method signature wrong, maybe because of many wiki and blog HTML editors kill angle bracket pairs <
and >
in code blocks:
function From(const Value: T): TValue; static;
Since the [Wayback] Delphi XE System.Rtti.TValue
documentation, the From
method signature is fixed (see the bold parts):
class function From<T>(const Value: T): TValue; static;
With the [Wayback] Delphi XE2 Rtti.TValue documentation, the unit got renamed from Rtti
into System.Rtti
and has not changed further.
When using TValue.From<T>()
, the AxisUnit
becomes this:
unit AxisUnit; interface type TAxis<T> = record fMin, fMax, fRange: T; fStart, fEnd, fExtent: single; function Calc( const AScalar: T ): single; strict private function AsSingle(const Value: T): single; end; implementation uses System.Rtti; function TAxis<T>.AsSingle(const Value: T): single; begin Result := TValue.From<T>(Value).AsExtended end; function TAxis<T>.Calc(const AScalar: T): single; var Offset: single; NormalisedOffset: single; ScaledOffset: single; begin Offset := AsSingle(AScalar) - AsSingle(fMin); NormalisedOffset := Offset / AsSingle(fRange); ScaledOffset := NormalisedOffset * fExtent; Result := fStart + ScaledOffset; end; end.
–jeroen