Arithmetic operations with generic types in Delphi

I am new to Delphi. For the project required by my company, I need to translate the code from our existing C ++ classes to Delphi. Some of these classes are templates, such as:

template <class T> struct APoint { T m_X; T m_Y; virtual void Add(T value); }; template <class T> void APoint<T>::Add(T value) { m_X += value; m_Y += value; } 

I use it, for example. with this code

 APoint<float> pt; pt.m_X = 2.0f; pt.m_Y = 4.0f; pt.Add(5.0f); 

and it works well.

Now I need to write equivalent code for Delphi. I tried to write a Delphi Generic class based on the C ++ code above:

 APoint<T> = record m_X: T; m_Y: T; procedure Add(value: T); end; procedure APoint<T>.Add(value: T); begin m_X := m_X + value; m_Y := m_Y + value; end; 

However, this code does not compile. I get this error:

E2015 Operator not applicable to this type of operand

AFAIK, this code should work, and I donโ€™t understand what is wrong with it. So can someone explain to me:

  • Why doesnโ€™t such code compile in Delphi?

  • What is the correct (and easiest) way in Delphi to create a template class that provides the Add() function as close as possible to the C ++ code and usage above?

EDITED 10/17/2016

Thanks for all the answers. Therefore, if I understood correctly, there is no way to create a style template similar to C ++, because Delphi imposes several restrictions that do not exist in C ++.

Based on this, I was looking for a workaround to achieve the goal I want. I found the following solution:

 IPoint<T> = interface procedure Add(value: T); end; APoint<T> = class(TInterfacedObject, IPoint<T>) m_X: T; m_Y: T; procedure Add(value: T); virtual; abstract; end; APointF = class(APoint<Single>) destructor Destroy; override; procedure Add(value: Single); reintroduce; end; destructor APointF.Destroy; begin inherited Destroy; end; procedure APointF.Add(value: Single); begin m_X := m_X + value; m_Y := m_Y + value; end; 

I use it, for example. with this code

 procedure AddPoint; var pt: IPoint<Single>; begin pt := APointF.Create; APointF(pt).m_X := 2.0; APointF(pt).m_Y := 4.0; APointF(pt).Add(5.0); end; 

and it works well. However, I find the style a bit heavy, for example. the need to use APointF (pt). So, regarding the above code, my questions are:

  • Is this a good solution? (that is, it is better to write a version of each entry for each type that I want to support, for example, APointF, APointI, APointD, ...)
  • Is there any way to simplify this code, for example. solution to call pt.m_X directly without converting APointF (pt)? ( NOTE Here I omitted the implementation of properties, even if I find them more elegant than direct access to a variable)
  • How about the speeches of this decision? (Ie this solution is much slower than directly adding the value m_X: = m_X +?)

Finally, I saw another solution in Delphi code, where you can implement a comparison of the equalities of two types of a general form as follows:

 function APoint<T>.IsEqual(const other: APoint<T>): Boolean; var comparer: IEqualityComparer<T>; begin Result := (comparer.Equals(m_X, other.m_X) and comparer.Equals(m_Y, other.m_Y)); end; 

I tried to read the code behind the scene, however I found it terribly complicated. So my questions are:

  • Is such a solution better than the one suggested above?
  • Is there a similar ready-to-use solution for mathematical operations?
  • How acceptable is the implementation of such a decision?

thank you for your responses

Hello

+5
source share
3 answers

Delphi generics do not support arithmetic operators that act on generic types. So that the compiler can accept the code, it needs to know that each operation in the generic type will be available after the instance is created.

General restrictions allow you to tell the compiler about the type capabilities. However, general restrictions do not allow you to tell the compiler that the type supports arithmetjc operations.

Unfortunately, what you are trying to do is simply not possible. Of course, you can create frameworks yourself that can use tools, such as interfaces, to do arithmetic, but this leads to reduced performance. If this is acceptable, then fine. Otherwise, you bite the bullet best and avoid generics.

Oh for C ++ templates.

+5
source

Delphi Generics are essentially different from the C ++ template and resemble more of their C # components.

In C ++, you can perform any operation on template types, and when creating an instance of the template, the compiler checks that the operation you perform in the template is available for the specific type that you are using. If not, you will get a compiler error.

In Delphi (and many other languages), you declare a generic type, possibly providing some declarative constraints, and these constraints โ€” base classes or an interface โ€” define the operations that you can perform for a generic type. During instantiation, the only check is if the declared type is suitable for restriction.

Perhaps Delphi may add restrictions for floating point types or sequence numbers, but this will provide very limited flexibility (changing the type of a floating or integer type that you can use in a generic instance). I personally do not consider this an important characteristic.

+6
source

Pointing this out for anyone looking for an Object Pascal solution to solve this common problem:

Please note that Free Pascal simply does not support exactly what they are trying to do here (even in the "Delphi-syntax compatibility" mode.)

As a person who used Free Pascal only for a long time, I honestly was very surprised that Delphi did not allow this at all, as soon as I realized that it was. This is a significant limitation of IMO.

Valid free Pascal code:

 program Example; // Using Delphi-mode here allows us to declare and use // generics with the same syntax as Delphi, as opposed to // needing the "generic" and "specialize" keywords that // FPC normally requires. {$mode Delphi} // To allow +=, another nice FPC feature... {$COperators On} type TGPoint<T> = record X, Y: T; // static class function instead of constructor here so we can inline it class function Create(constref AX, AY: T): TGPoint<T>; static; inline; // define our operator overload class operator Add(constref Left, Right: TGPoint<T>): TGPoint<T>; inline; end; class function TGPoint<T>.Create(constref AX, AY: T): TGPoint<T>; begin with Result do begin X := AX; Y := AY; end; end; class operator TGPoint<T>.Add(constref Left, Right: TGPoint<T>): TGPoint<T>; begin with Result do begin X := Left.X + Right.X; Y := Left.Y + Right.Y; end; end; var SP: TGPoint<String>; begin SP := TGPoint<String>.Create('Hello, ', 'Hello, '); SP += TGPoint<String>.Create('world!', 'world!'); with SP do begin WriteLn(X); WriteLn(Y); end; end. 

The course program prints:

 Hello, world! Hello, world! 
+1
source

Source: https://habr.com/ru/post/1258257/


All Articles