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
Syntax highlighted sources
Full program
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
program DelphiMathAndGenerics; | |
type | |
TAxis<T: record> = 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. |
Split in unit and realisation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
unit AxisUnit; | |
interface | |
type | |
TAxis<T: record> = record | |
fMin, fMax, fRange: T; | |
fStart, fEnd, fExtent: single; | |
function CalcCasted( const AScalar: T ): single; | |
function CalcPlain( const AScalar: T ): single; | |
end; | |
implementation | |
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; | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
program DelphiMathAndGenerics; | |
uses | |
AxisUnit in 'AxisUnit.pas'; | |
var | |
rXAxis: TAxis<TDateTime>; | |
rYAxis: TAxis<single>; | |
begin | |
end. |