Arithmetic operations with generic types in Delphi

[亡魂溺海] 提交于 2019-12-19 07:59:28

问题


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 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 e.g. with this code

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

and this 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 operand type

AFAIK this code should work, and I don't understand what is wrong with it. So can anybody explain to me:

  1. Why a such code does not compile in Delphi?

  2. What is the correct (and simplest) way in Delphi to create a template class that provides an Add() function, as closest as possible to the C++ code and usage above?

EDITED on 17.10.2016

Thanks for all the replies. So if I understood correctly, there is no way to create a c++-like style template, because Delphi imposes several constraints that not exists in c++.

Based on that, I searched a workaround to reach the objective 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 e.g. 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 this works well. However I find the style a little heavy, e.g. the necessity to use APointF(pt). So, in relation to code above, my questions are:

  1. Is this solution a good solution? (i.e. better to write a version of each record for each type I want to support, like e.g APointF, APointI, APointD, ...)
  2. Is there a way to simplify this code, e.g. a solution to call pt.m_X directly without the APointF(pt) conversion? (NOTE I omitted here the implementation of properties, even if I think them more elegant than accessing the variable directly)
  3. What about the performances of this solution? (I.e. is this solution drastically slower than a direct m_X := m_X + value addition?)

Finally, I saw another solution in the Delphi code, where it is possible to implement an equality comparison of 2 generic types this way:

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:

  1. Is a such solution better than the one above proposed?
  2. Is there a similar ready-to-use solution for mathematical operations?
  3. Are the performance of a such solution acceptable?

Thanks in advance for your replies

Regards


回答1:


Delphi generics do not support arithmetic operators that act on generic types. In order for the compiler to accept the code it needs to know that each operation on a generic type is going to be available upon instantiation.

Generic constraints allow you to tell the compiler what capabilities the type has. However generic constraints do not allow you to tell the compiler that the type supports arithmetjc operators.

Unfortunately what you are trying to do is simply not possible. For sure you can construct frameworks yourself that can use tools like interfaces to get the arithmetic performed but doing so gives up performance. If that is acceptable then fine. Otherwise you are best biting the bullet and avoiding generics here.

Oh for C++ templates.




回答2:


Delphi Generics are intrinsically different from C++ template, and resemble more their C# counterpart.

In C++ you can do any operation on template types, and at time of the template instantiation the compiler checks that the operation you are performing in the template are available for the specific type you are using. If not you get a compiler error.

In Delphi (and many other languages) you declare a generic type possibly providing some declarative constraints, and those constraints -- base classes or interface -- determine the operations you can do on the generic type. At instantiation time, the only check is if the declared type fits the constraint.

Arguably, the Delphi language could add constraints for floating point or ordinal types, but this would provide a very limited flexibility (changing the floating or integer type you can use in the generic instance). I personally don't regard this as a critical feature.




回答3:


Pointing this out for anyone else looking for an Object Pascal solution to this general issue:

Note that Free Pascal quite simply does support exactly what they're trying to do here (even in "Delphi-syntax compatibility" mode.)

As someone who had only used Free Pascal for a long time, I was honestly very surprised that Delphi doesn't allow this at all, once I realized that was the case. It's a significant limitation 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 program of course prints:

Hello, world!
Hello, world!


来源:https://stackoverflow.com/questions/40059579/arithmetic-operations-with-generic-types-in-delphi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!