在Delphi中使用泛型类型的算术运算

时间:2016-10-15 13:25:28

标签: delphi templates generics math addition

我是Delphi的新手。对于我公司要求的项目,我需要将现有C ++类中的一些代码翻译成Delphi。其中一些类是模板,例如:

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;
}

我用它,例如使用此代码

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

这很有效。

现在我需要为Delphi编写等效的代码。我尝试编写一个Delphi Generic类,基于上面的C ++代码:

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;

但是这段代码不能编译。我收到这个错误:

  

E2015运算符不适用于此操作数类型

AFAIK这个代码应该有效,我不明白它有什么问题。所以有人可以向我解释一下:

  1. 为什么这样的代码不能在Delphi中编译?

  2. Delphi中创建一个提供Add()函数的模板类的正确(和最简单)方法是什么,尽可能接近上面的C ++代码和用法?

  3. 于2016年10月17日编辑

    感谢所有回复。因此,如果我理解正确,就无法创建类似c ++的样式模板,因为Delphi强加了c ++中不存在的几个约束。

    基于此,我搜索了一个解决方法以达到我想要的目标。我找到了以下解决方案:

    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;
    

    我用它,例如使用此代码

    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;
    

    这很有效。但是我发现这种风格有点重,例如使用APointF(pt)的必要性。所以,关于上面的代码,我的问题是:

    1. 此解决方案是一个很好的解决方案吗? (即最好为我想支持的每种类型编写每个记录的版本,例如APointF,APointI,APointD,......)
    2. 有没有办法简化此代码,例如没有APointF(pt)转换直接调用pt.m_X的解决方案? (注意我在这里省略了属性的实现,即使我认为它们比直接访问变量更优雅)
    3. 这个解决方案的性能如何? (即,这个解决方案比直接m_X:= m_X +值的添加要慢得多?)
    4. 最后,我在Delphi代码中看到了另一个解决方案,可以通过这种方式实现2种泛型类型的相等比较:

      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;
      

      我试着阅读幕后的代码,但是我发现它非常复杂。所以,我的问题是:

      1. 这样的解决方案比上面提出的更好吗?
      2. 是否有类似的即用型数学运算解决方案?
      3. 此类解决方案的性能是否可以接受?
      4. 提前感谢您的回复

        此致

3 个答案:

答案 0 :(得分:5)

Delphi Generics与C ++模板本质上不同,更像是C#对应物。

在C ++中,您可以对模板类型执行任何操作,并且在模板实例化时,编译器会检查您在模板中执行的操作是否适用于您正在使用的特定类型。如果不是,则会出现编译错误。

在Delphi(和许多其他语言)中,您声明一个泛型类型可能提供一些声明性约束,而这些约束 - 基类或接口 - 确定您可以对泛型类型执行的操作。在实例化时,唯一的检查是声明的类型是否适合约束。

可以说,Delphi语言可以为浮点或序数类型添加约束,但这会提供非常有限的灵活性(更改可在通用实例中使用的浮点或整数类型)。我个人并不认为这是一个关键特征。

答案 1 :(得分:4)

Delphi泛型不支持对泛型类型起作用的算术运算符。为了让编译器接受代码,它需要知道泛型上的每个操作在实例化时都可用。

通用约束允许您告诉编译器该类型具有哪些功能。但是,通用约束不允许您告诉编译器该类型支持arithmetjc运算符。

不幸的是,你想做的事情根本不可能。当然,您可以自己构建框架,可以使用类似接口的工具来执行算法,但这样做会放弃性能。如果这是可以接受的那么好。否则你最好咬紧牙关,避免在这里使用仿制药。

哦,对于C ++模板。

答案 2 :(得分:1)

向其他寻求此一般问题的对象Pascal解决方案的人指出这一点:

请注意,Free Pascal非常简单地做到,完全支持他们在此所做的尝试(甚至在“ Delphi语法兼容”模式下)。

作为一个只使用Free Pascal很长时间的人,我真的很惊讶Delphi根本不允许这样做。这是IMO的重要限制。

有效的免费Pascal代码:

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.

该程序打印:

Hello, world!
Hello, world!