1
votes

There is an old hack to making semi-generic containers in Delphi using include-files.

See http://www.delphikingdom.com/asp/viewitem.asp?catalogid=453&mode=print and start with 3d listing to grasp the idea.

However having two inter-dependent INC-files in units' interface and implementation sections leads to troubles: seems like XE2 compiles those include files as independent units instead and implementation cannot find functions declared in interface. It does not happen every time, but i failed to determine the conditions and thus failed to make a workaround.

Trying to re-formulate this in Delphi generics unit with as little changes as possible (i had to move huge legacy project to XE2 and "working" should be 1st, optimizations and refactoring later), i stuck into the following pit:

  TemplateList<_DATA_TYPE_> = class  
  public
    const MaxListSize = Maxint div (sizeof(Integer)*sizeof(_DATA_TYPE_));    
    type
      TIntList = array[0..MaxListSize - 1] of _DATA_TYPE_;   
      PIntList = ^TIntList;                                  
  private
    FList: PIntList;
    FCount: Integer;

This gives an error that Low-bound for TIntList is upper than High-bound. Which, i think, means that const MaxListSize is evaluated to zero, but the TIntType tries to be evaluated immediately, not when actually instantiating the type.

I wonder if XE3 or XE4 fixed this. And if there is a way to make this compile in XE2 without major re-working

PS. making the array 0..0 and suppressing bounds checking is usual solution, yet it make a lot of fragile non-checked code. Maybe i'd end up using real TList or TList<integer\> instead...

PPS. Funny thing, reformulating inner type with copy-paste

TIntList = array[0..Maxint div (sizeof(Integer)*sizeof(_DATA_TYPE_)) - 1] of _DATA_TYPE_;

changes the error into "const expression required".

So the same expression is considered const-enough in one branch of compiler and non-const in another... I wonder if it constitutes an inconsistency bug per se.

1
making the array 0..0 and suppressing bounds checking is usual solution, yet it make a lot of fragile non-checked code I don't think so. In what way is that any more fragile than 0..MaxPossibleIndex?David Heffernan

1 Answers

3
votes

The problem with the compiler appears to be that at the generic phase of the compilation, sizeof(_DATA_TYPE_) is not known. And so the compiler appears to use a place holder value of 0. By the time you instantiate the generic type, sizeof(_DATA_TYPE_) is replace by the true value. But it's too late. The array type bounds checking is performed at the generic phase of the compilation at which point sizeof(_DATA_TYPE_) is 0 and so the compiler gags.

That this is the case can be seen by the following code:

type
  TemplateList<_DATA_TYPE_> = class
  public
    const
      SizeOfDataType = sizeof(_DATA_TYPE_);
      MaxListSize = Maxint div (sizeof(Integer)*SizeOfDataType);
  end;

which produces this compiler error:

[dcc32 Error]: E2098 Division by zero

But, if you try this variant:

{$APPTYPE CONSOLE}

type
  TemplateList<_DATA_TYPE_> = class
  public
    const
      SizeOfDataType = sizeof(_DATA_TYPE_);
  end;

begin
  Writeln(TemplateList<Integer>.SizeOfDataType);
  Writeln(TemplateList<Double>.SizeOfDataType);
  Readln;
end.

the output is:

4
8

This shows that your constant declaration has a place holder value for the array type bounds checking, but has a true value once the generic has been instantiated. So, if the compiler postponed array type bounds checking until instantiation, then all would be well in terms of the compiler warning. But even then, the code would not do what you expect and desire. This program:

{$APPTYPE CONSOLE}

type
  TemplateList<_DATA_TYPE_> = class
  public
    const
      SizeOfDataType = sizeof(_DATA_TYPE_);
    type
      TMyArray = array [0..SizeOfDataType] of _DATA_TYPE_;
  end;

begin
  Writeln(high(TemplateList<Integer>.TMyArray));
  Writeln(high(TemplateList<Double>.TMyArray));
  Readln;
end.

produces somewhat undesirable output:

0
0

So it seems that not only are the array bounds checked at the generic phase of compilation, but the array bounds are fixed at that phase, and fixed using the place holder value of the type parameter size. What this means is that you cannot hope to achieve array bounds that vary based on the size of the data type.

The same behaviour is present in XE3, and I don't have XE4 at hand to check there.

I personally feel that this is a design flaw in the compiler that warrants a QC report.


In my opinion, the only viable way to resolve this is to give up trying to specify array bounds. I would declare it like this:

type
  TemplateList<_DATA_TYPE_> = class
  public
    type
      TIntList = array[0..0] of _DATA_TYPE_;
      PIntList = ^TIntList;
  private
    FList: PIntList;
    FCount: Integer;
  end;

Obviously you'll need to disable range checking in this unit, but that's no real hardship since range checking doesn't do you any favours for the original code.