В этом обзоре мы рассмотрим новую функциональность, недавно добавленную в Free Pascal Compiler (FPC) — неявные специализации шаблонных функций. Эта функция значительно упрощает использование шаблонных функций и процедур, позволяя компилятору автоматически определять необходимые специализации на основе переданных параметров. В статье мы рассмотрим, как работает эта функция, её ограничения, примеры использования и возможные проблемы.
Что такое неявные специализации шаблонных функций?
Неявные специализации шаблонных функций позволяют вам использовать шаблонные функции и процедуры без явного указания специализаций («<...>» в режиме Delphi и «specialize ...<...>» в режимах, не являющихся Delphi), если компилятор может определить правильные параметры типа для шаблонной функции.
Как это работает?
Рассмотрим пример шаблонной функции:
generic function Add<T>(aArg1, aArg2: T): T;
begin
Result := aArg1 + aArg2;
end;
До введения этой функциональности вы могли использовать эту функцию только с явной специализацией:
Компилятор автоматически определяет типы параметров шаблона на основе переданных параметров (в порядке слева направо). В некоторых случаях компилятор может выбрать другой тип, чем вы ожидали, особенно если вы используете константные значения. Например, в примере выше компилятор может выбрать 8-битный знаковый тип вместо LongInt, так как компилятор предпочитает знаковые типы. Вы можете зафиксировать конкретный тип, явно специализируя метод или вставляя явное преобразование типа.
Примеры использования
Рассмотрим несколько примеров использования неявных специализаций шаблонных функций:
Простые параметры:
generic function ArrayFunc<T>(aArg: specialize TArray<T>): T;
var
e: T;
begin
Result := Default(T);
for e in aArg do
Result := Result + e;
end;
type
generic TTest<T> = function(aArg: T): T;
generic function Apply<T>(aFunc: specialize TTest<T>; aArg: T): T;
begin
Result := aFunc(aArg);
end;
function StrFunc(aArg: String): String;
begin
Result := UpCase(aArg);
end;
function NegFunc(aArg: LongInt): LongInt;
begin
Result := - aArg;
end;
begin
Writeln(ArrayFunc([1, 2, 3])); // выведет 6
Writeln(ArrayFunc(['Hello', 'FPC', 'World'])); // выведет HelloFPCWorld
Writeln(Apply(@StrFunc, 'Foobar')); // выведет FOOBAR
Writeln(Apply(@NegFunc, 42)); // выведет -42
end.
Массивы и функциональные переменные:
generic function ArrayFunc<T>(aArg: specialize TArray<T>): T;
var
e: T;
begin
Result := Default(T);
for e in aArg do
Result := Result + e;
end;
type
generic TTest<T> = function(aArg: T): T;
generic function Apply<T>(aFunc: specialize TTest<T>; aArg: T): T;
begin
Result := aFunc(aArg);
end;
function StrFunc(aArg: String): String;
begin
Result := UpCase(aArg);
end;
function NegFunc(aArg: LongInt): LongInt;
begin
Result := - aArg;
end;
begin
Writeln(ArrayFunc([1, 2, 3])); // выведет 6
Writeln(ArrayFunc(['Hello', 'FPC', 'World'])); // выведет HelloFPCWorld
Writeln(Apply(@StrFunc, 'Foobar')); // выведет FOOBAR
Writeln(Apply(@NegFunc, 42)); // выведет -42
end.
Использование неявных специализаций с наследованием:
program Project1;
{$mode objfpc}{$H+}
{$ModeSwitch implicitfunctionspecialization}
uses
Generics.Collections;
generic procedure Foo<T>(lst: specialize TEnumerable<T>);
begin
end;
var
lst: specialize TList<Integer>; // Наследуется от TEnumerable
begin
Foo(lst); // Ошибка
specialize Foo<Integer>(lst); // работает
end.
В этом примере компилятор не может автоматически определить специализацию для TList<Integer>, так как TList<Integer> наследуется от TEnumerable. Чтобы избежать этой ошибки, можно явно указать специализацию.
Ограничения
Несмотря на полезность неявных специализаций, есть несколько ограничений:
Все параметры шаблона должны быть использованы в объявлении функции. Параметры шаблона, которые используются только в реализации функции, не поддерживаются.
Параметры с шаблонными типами не могут быть параметрами по умолчанию. Они должны быть использованы в вызове функции или их тип должен быть зафиксирован параметром слева.
Шаблонные функции не могут иметь константные параметры шаблона. Это ограничение может быть снято в будущем, например, для статических массивов или файловых типов.
Тип результата не учитывается. Если только тип результата функции является шаблонным, неявная специализация не будет работать.
Не поддерживаются указатели на функции с неявными специализациями. Указатели на явно специализированные функции также не поддерживаются в текущей версии.
Компилятор игнорирует неспециализированные шаблонные функции, если они не могут быть специализированы. Если объявление функции может быть специализировано, но реализация не может, это приведет к ошибке компиляции.
Проблемы и решения
Во время использования неявных специализаций могут возникнуть некоторые проблемы. Например, если вы разделяете перегруженные функции на несколько модулей, компилятор может не уметь правильно определить, какую функцию вызвать.
Пример:
unit Unit1;
{$mode Delphi}
interface
function Test(const AString: Rawbytestring): Integer; overload;
implementation
function Test(const AString: Rawbytestring): Integer;
begin
end;
end.
unit Unit2;
{$mode Delphi}
interface
function Test<T>(const AList: TArray<T>): Integer; overload;
implementation
function Test<T>(const AList: TArray<T>): Integer;
begin
end;
end.
program Project1;
{$mode Delphi}
uses unit1, unit2;
var
S: Rawbytestring;
I: Integer;
begin
I := Test(S);
end.
В этом примере компилятор не может определить, какую функцию вызвать, так как обе функции находятся в разных модулях. Решение этой проблемы заключается в объединении перегруженных функций в одном модуле или явном указании специализации.
Заключение
Неявные специализации шаблонных функций в FPC значительно упрощают использование шаблонных функций и процедур. Однако, из-за некоторых ограничений и возможных проблем, важно внимательно проверять код и быть готовым к необходимости явного указания специализаций. Надеемся, что в будущих версиях FPC эти ограничения будут сняты, и использование шаблонных функций станет еще более удобным и мощным инструментом для разработчиков на Delphi и Pascal.
Context: Введение в новую функциональность Free Pascal Compiler — неявные специализации шаблонных функций, которая позволяет автоматически определять типы шаблонов на основе переданных параметров, упрощая использование шаблонов, но с некоторыми ограничен
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS