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
Read the rest of this entry »
Like this:
Like Loading...