Подтвердить что ты не робот

Почему две псевдонимы "массив строки" обрабатывались по-разному?

В Pascal существует два типа объявлений типов:

  • псевдонимы типов: type NewName = OldType
  • type creation: type NewType = type OldType

Первый просто создает удобную стенографию, например typedef в C. Эти псевдонимы совместимы друг с другом и с их оригинальным типом. Созданные типы преднамеренно несовместимы и не могут быть смешаны без явного и небезопасного по определению типа.

var
  nn: NewName; nt: NewType; ot: OldType;
...
  nn := ot; // should work
  nt := ot; // should break with type safety violation error.

  nt := NewType(ot); // Disabling type safety. Should work even if 
  // it has no sense semantically and types really ARE incompatible.

Это основы Pascal, как я их понимаю.

Теперь рассмотрим один определенный тип и два его псевдонима:

  • массив System.Types.TStringDynArray = строки;
  • System.TArray < Т >= массив T;
    • в частности, что означает TArray <string>= массив строки; по определению.

Теперь давайте возьмем функцию, возвращающую прежний псевдоним типа, и передаем его результат функции, ожидающей последнего:

uses Classes, IOUtils;

 TStringList.Create.AddStrings(
    TDirectory.GetFiles('c:\', '*.dll') );

 TStringList.Create.AddStrings(
     TArray<string>( // this is required by compiler - but why ???
         TDirectory.GetFiles('c:\', '*.dll') ) );

1-й фрагмент не будет компилироваться из-за нарушения типов. Второй счастливо компилируется и работает, но хрупка в отношении будущих изменений типа и является избыточным.

QC сообщает, что компилятор прав, а дизайн RTL ошибочен. http://qc.embarcadero.com/wc/qcmain.aspx?d=106246

Почему компилятор здесь? Почему эти псевдонимы несовместимы? Даже сама разработка RTL предполагает, что они считаются совместимыми!

PS. Дэвид предложил еще более простой пример, не используя TArray <T>

 type T1 = array of string; T2 = array of string;

 procedure TForm1.FormCreate(Sender: TObject);
  function Generator: T1;
    begin Result := T1.Create('xxx', 'yyy', 'zzz'); end;
  procedure Consumer (const data: T2);
    begin
      with TStringList.Create do 
      try
        AddStrings(data);
        Self.Caption := CommaText;
      finally
        Free;
      end;
    end;
  begin
    Consumer(Generator);
  end;

То же самое получить без объяснения...

ПФС. Сейчас есть несколько документов. Я хочу подчеркнуть одно: хотя это ограничение может быть косвенно унаследовано из отчета Паскаля 1949 года, сегодня 2012 год, а Delphi очень сильно отличался от школьных лабораторий полвека назад. Я назвал несколько БАД эффектов сохранения этих ограничений и все же не видел ничего хорошего.

Ироническая вещь, что это ограничение может быть отменено без нарушения правил Паскаля: в Паскале нет такого нестрого зверя, как открытые массивы и динамические массивы. Поэтому пусть эти исходные фиксированные массивы будут ограничены по своему усмотрению, но открытые массивы и динамические массивы не являются гражданами Паскаля и не обязаны ограничиваться его кодовой книгой!

Пожалуйста, сообщите Эмбе в QC или, возможно, даже здесь, но если вы просто проходите мимо, не выражая свое мнение - ничего не изменится!

4b9b3361

Ответ 1

Ключом к пониманию этой проблемы является тема Тип совместимости и идентификация в руководстве по языку. Я предлагаю вам хорошо прочитать эту тему.

Также полезно упростить пример. Включение дженериков в пример служит главным образом для усложнения и путаницы.

program TypeCompatibilityAndIdentity;
{$APPTYPE CONSOLE}

type
  TInteger1 = Integer;
  TInteger2 = Integer;
  TArray1 = array of Integer;
  TArray2 = array of Integer;
  TArray3 = TArray1;

var
  Integer1: TInteger1;
  Integer2: TInteger2;
  Array1: TArray1;
  Array2: TArray2;
  Array3: TArray3;

begin
  Integer1 := Integer2; // no error here
  Array1 := Array2; // E2010 Incompatible types: 'TArray1' and 'TArray2'
  Array1 := Array3; // no error here
end.

Из документации:

Когда один тип идентификатора объявляется с использованием другого идентификатора типа, без квалификации, они обозначают один и тот же тип.

Это означает, что TInteger1 и TInteger2 являются одинаковыми, и действительно являются тем же типом, что и Integer.

Немного дальше в документации:

Языковые конструкции, которые функционируют как имена типов, обозначают разные типы каждый раз, когда они происходят.

В эту категорию попадают объявления TArray1 и TArray2. И это означает, что эти два идентификатора обозначают разные типы.

Теперь нам нужно посмотреть раздел, посвященный совместимости. Это дает набор правил для определения того, совместимы ли два типа или совместимы ли они. Фактически мы можем сократить это обсуждение, обратившись к другой теме справки: Структурированные типы, типы и назначения массивов, в которых четко указано:

Массивы совместимы по назначению, только если они одного типа.

Это дает понять, почему присвоение Array1 := Array2 приводит к ошибке компилятора.

В вашем коде были просмотрены параметры передачи, но моя сосредоточена на назначении. Проблемы одинаковы, потому что в разделе справки Calling Procedures and Functions объясняется:

При вызове процедуры помните, что:

  • выражения, используемые для передачи типизированных параметров const и value, должны быть совместимы с соответствующими формальными параметрами.
  • .......

Ответ 2

Delphi - строго типизированный язык. Это означает, что идентичные (в данном случае я имею в виду, что их определения выглядят точно так же), типы не совместимы с назначением.

Когда вы пишете array of <type>, вы определяете тип, а не псевдоним. Как уже сказал Дэвид в своем комментарии, два одинаковых типа типа

type 
  T1 = array of string; 
  T2 = array of string;

не совместимы с назначением.

То же самое касается

type
  TStringDynArray = array of string;
  TArray<T> = array of string;

Часто люди забывают о несовместимости одинаковых типов, и я предполагаю, что они это сделали, когда представили IOUtils, например. Теоретически определение TStringDynArray должно быть изменено на TStringDynArray = TArray<string>, но я думаю, что это могло бы вызвать другие проблемы (не говоря об ошибках с generics...).

Ответ 3

У меня также была та же проблема с Delphi, где я хотел передавать значения из одного идентичного массива в другой. У меня не только были проблемы с несовместимостью с двумя одинаковыми назначениями массива, но также я не мог использовать процедуру "Копировать()" . Чтобы обойти эту проблему, я обнаружил, что вместо этого я мог бы использовать указатель на массив type массива строки.

Например:

type RecArry = array of array of string
     end;
var TArryPtr : ^RecArry;

Теперь я могу передавать значения из любого фиксированного массива в другой идентичный массив без каких-либо проблем с совместимостью или функциями. Например:

TArryPtr := @RecArry.LstArray //This works!
TArryPtr := @LstArray         //This also works!

С помощью этого шаблона назначения массива я могу без проблем работать со всеми двумерными массивами. Однако следует понимать, что при доступе к этому типу указателя массива строк создается дополнительный элемент, так что, когда мы ожидаем, что этот тип массива 2D массива ниже, например:

Two_Dimensional_Fixed_Array[10][0]

Теперь мы получим дополнительный отрегулированный элемент, как показано здесь:

New_Two_Dimensional_Fixed_Array[10][1]    

Это означает, что для доступа к массиву указателей мы должны использовать немного сложный код, потому что все заполненные элементы в Two_Dimensional_Fixed_Array [10] [0] перемещены вниз, так что они смещены на 1, как в New_Two_Dimensional_Fixed_Array [10 ] [1].

Поэтому, когда мы обычно находим значение 'X' в Two_Dimensional_Fixed_Array [1] [0], оно теперь будет найдено здесь в TArryPtr [0 ] [1].

Свою торговлю мы все должны жить!

Еще одно важное замечание - определение массива указателей при его объявлении. Когда массив указателей объявляется типом, компилятор Borland не позволит массиву указателей иметь тот же размер элемента, что и массив, на который он указывает. Например, если массив объявлен как:

Orig_Arry : array [1..50,1] of string;

Массив указателя, который должен указывать на него, будет объявлен следующим образом:

Type Pntr_Arry : array [1..50,2] of string;

Вы заметили дополнительный элемент? Я предполагаю, что компилятор Borland должен расширить указатель массива, чтобы разрешить адрес указателя.