Delphi 中泛型的算术运算

Arithmetic operations with generic types in Delphi

我是 Delphi 的新人。对于我公司需要的一个项目,我需要将一些代码从我们现有的 C++ classes 翻译成 Delphi。其中一些 class 是模板,例如:

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 class,基于上面的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 Operator not applicable to this operand type

据我所知这段代码应该可以工作,但我不明白它有什么问题。那么谁能给我解释一下:

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

  2. 在 Delphi 中创建提供 Add() 功能的模板 class 的正确(和最简单)方法是什么,尽可能接近以上C++代码及用法?

2016 年 10 月 17 日编辑

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

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

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 + 增值慢得多吗?)

最后,我在 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. 这样的解决方案的性能是否可以接受?

提前感谢您的回复

此致

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

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

很遗憾,您尝试做的事情根本不可能。当然,您可以自己构建框架,这些框架可以使用接口之类的工具来执行算术运算,但这样做会牺牲性能。如果那是可以接受的,那很好。否则你最好咬紧牙关,避免在这里使用泛型。

哦,C++ 模板。

Delphi 泛型本质上不同于 C++ 模板,更类似于它们的 C# 模板。

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

在 Delphi(以及许多其他语言)中,您声明一个泛型类型可能会提供一些声明性约束,而这些约束——基础 类 或接口——决定了你可以对其执行的操作通用类型。在实例化时,唯一的检查是声明的类型是否符合约束条件。

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

向正在寻找此一般问题的 Object Pascal 解决方案的任何其他人指出这一点:

请注意,Free Pascal 非常简单 确实 支持他们在这里尝试做的事情(即使在 "Delphi-syntax compatibility" 模式下。)

作为一个只使用 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!