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.
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.
Related
I am using the SmartPointer in http://members.adug.org.au/2011/12/05/smart-pointers/
I defined a IStringList:
type
IStringList = ISmartPtr<TStringList>;
I may then use as follow without memory leak prompt:
var S: IStringList;
begin
S := TSmartPtr<TStringList>.Create();
S.Add('abc');
end;
If I use the IStringList as result data type in a function:
function GetList: IStringList;
begin
Result := TSmartPtr<TStringList>.Create();
Result.Add('abc'); // E2010
end;
I get a compiler error:
[dcc32 Error] Unit2.pas(31): E2010 Incompatible types: 'SmartPointer.ISmartPtr<System.Classes.TStringList>' and 'Procedure of object'
A workaround solution would be:
Result.Invoke.Add('abc');
But that defeat the purpose of syntax cleanliness of using SmartPointer. Is there a solution?
It's quite an interesting one. For whatever reason, the compiler treats Result differently from other variables. I see no good reason for that, so this feels like a compiler bug.
I see a few workarounds:
Declare a local variable, use that, and finish the function by assigning that local variable to Result.
Using parens to call the function: Result().Add('abc').
Using a better smart pointer. For instance, a smart pointer that is not based on method invocation would be a good start. A generic record with an implicit conversion operator would presumably work.
Find a version of the compiler without the bug, if indeed it is a compiler bug.
Give up on smart pointers and write traditional style code. Every time I contemplate using smart pointers in Delphi I conclude that I'd rather see the explicit try/finally blocks and understand when my objects are created and destroyed.
FWIW, you can greatly simplify the issue by stripping out the smart pointer code, and removing the generic types. Here is the cleanest SSCCE that I can concoct:
{$APPTYPE CONSOLE}
type
TFunc = reference to function: TObject;
procedure Foo;
var
F: TFunc;
begin
F.ClassName; // compiles
end;
function Bar: TFunc;
begin
Result().ClassName; // compiles
Result.ClassName; // [dcc32 Error] E2003 Undeclared identifier: 'ClassName'
end;
begin
end.
I'm now convinced that this is a compiler bug.
It is presumably related to the rather unusual language feature of Delphi that means the function call parentheses can be omitted for a function that has no parameters. This convenience sometimes brings with it ambiguity. However, in this case there is no ambiguity. The . operator has no meaning when applied to a TFunc<TObject> and so the only way to interpret these statements is that the function is called, and the . operator applied to the returned value.
Bug report: QC123218.
{ I'm not allowed to comment... -.-' Feel free making it one. }
I'm afraid there is no way without defeating the purpose of smart pointers. I tried to solve your problem as part of trying to find a way to assign an anonymous method to an interface variable or parameter (SO question from July 2013). Coming back to it every now and then or asking around didn't help in finding a solution.
Delphi.
Why
type
myInt = Integer;
myNewInt = -2147483648..2147483647;
var
a: myint;
b: myNewInt;
begin
a := b;
end;
It is compiled normally though types formally different - one is declared here, another undertakes from other module. And if
uses
windows;
type
_FILETIMEA = record // Copy from windows.pas
dwLowDateTime: DWORD;
dwHighDateTime: DWORD;
end;
var
x: _FILETIMEA;
y: windows._FILETIME;
begin
x := y;
end;
Will cause a compilation error (in line x:=y; [DCC Error] ... E2010 Incompatible types: 'windows._FILETIME' and '_FILETIMEA'), though type _FILETIMEA = Windows._FILETIME ?
Delphi doesn't support duck typing. You have 2 different records. They just look a like, but they are 2 different types for the compiler. If you want to assign the two variables you have to type cast them what is possible because they have the same size.
x := _FILETIMEA(y);
Because you're not doing the same thing. :) Delphi is strongly typed (there aren't many exceptions - almost everything has a specific type).
type
myInt = Integer;
myNewInt = -2147483648..2147483647;
There is nothing incompatible about these types. They're both the same type (integer), and support the same range of values. There's no conflict about allowing the assignment of one to the other.
// Your unit name
unit GuTypes;
Type
_FILETIMEA = record // Copy from windows.pas
dwLowDateTime: DWORD;
dwHighDateTime: DWORD;
end;
This is a new type, defined by your code (even though it's identical to the one in Windows.pas, it's not the same). It's GuTypes._FileTimeA, which is not the same as Windows._FileTimeA.
This is actually a good thing, because it allows different units to use the same typenames without conflict; you can prefix with the unit name as a "namespace" to clarify which one you mean to use. For instance, if you have yours defined in GuTypes, and Windows has one declared there, in a third unit MyAppCode, you can do this safely:
var
GFileTime: GuTypes._FILETIMEA;
WFileTime: Windows._FILETIMEA;
Even if one of the types changes, your code is safe from side effects, because the two types can't accidentally be interchanged.
You can typecast to force the assignment to work, but that's usually a bad idea; it defeats the whole purpose of using a strongly typed language. Typecasting tells the compiler "I'm smarter than you, and I know this is wrong but I want you to ignore it and do it anyway."
A better solution would be either to declare your variable as you did the y in your example (Windows._FILETYPEA), or to declare a compatible type (type TMyFileTimeA = Windows._FILTETIMEA;).
See the XE2 Wiki Pages on Type Compatibility and Identity. Look specifically at the Assignment Compatibility section.
Using Delphi 2010...
I have a set of binary properties I want to group together. I have defined it as such...
type
TTableAttributeType = (
tabROOT = 1,
tabONLINE = 2,
tabPARTITIONED = 3,
tabCOMPRESSED = 4,
);
// Make a set of of the Table Attribute types...
type
TTableAttrSet = Set of TTableAttributeType;
In my MAIN.PAS unit, I can create a variable of type TTableAttrSet.
Another Unit, UTILS.PAS needs to understand the TTableAttrSet type as well. That is taken care of by the USES clauses...
Main USES Util...
Util USES Main (The 2nd uses clauses, under implementation section, so I don't get circular reference problems....
So far so good. My problem is that I need to pass a var variable of type TTableAttrSet FROM main to Utils.
In main.pas
procedure TForm1.Button1Click(Sender: TObject);
var
TabAttr : TTableAttrSet;
begin
TestAttr (TabAttr);
end;
and in UTILS.PAS, I have
procedure TestAttr(var Attr: TTableAttrSet);
begin
Attr := [tabROOT, tabCOMPRESSED];
end;
When I try this, I run into several problems...
Problem 1). When I define my procedure definition at the top of utils.pas,
procedure TestAttr(var Attr: TTableAttrSet);
I get the error that TTableAttrSet is an Undeclared Identifier. This makes sense because the definition is in Main.pas, and the 'uses Main.pas' is AFTER my procedure definitions. How do I get around this? For now, I have duplicated the TTableAttrSet type definition at the top of the Utils.pas file as well as Main.pas, but this does not 'seem the right way'.
Problem 2). The bigger issue that I am running into is a compile error. on the calling line in Main.pas
TestAttr(TabAttr);
I get the error 'Types of actual and formal var parameters must be identifical'. To my knowledge they are identical. What is the compiler complaining about?
The simple solution is to move the declaration of TTableAttributeType to the Utils unit. You can't declare it twice because then you have two distinct types. That's no use to you, you need only a single type.
This solution will work so long as the Main unit does not need to reference TTableAttributeType in its interface section. Since the Utils unit clearly needs to do so then that would create a circular dependency between unit interface sections which is illegal.
If both the Main and Utils units need to reference TTableAttributeType in their interface sections then you need to create another unit that just contains type declarations. That unit could be used by both Utils and Main in their interface sections.
In some code I am fixing up, which makes heavy use of generics and interfaced types, I am getting error
E2134, Type '<void>' has no type info.
I believe it is because I am in the middle of a refactor where some deeply nested set of units that all use generics are out of sync, but the error is not happening in a place where I can make use of the error message to fix the code, because there is nothing wrong with the code, at the location where the error is appearing.
Here is the context, mocked up, because I can not post the code, there is too much:
unit GenericThing;
...
interface
...
type
...
IThingListOf<ThingT> = interface( IThingContainer )
function getEnumerator: TEnumerator<ThingT>;
function getCount: Integer;
function getThing( Index: integer ): ThingT;
function getFirst: ThingT;
function IndexOf( value: ThingT): integer;
function addItem( const Thing: ThingT ): ThingT;
function removeItem( const Thing: ThingT ): Integer;
procedure clear;
procedure Sort; overload;
procedure Sort(const AComparer: IComparer<ThingT>); overload;
property Count: integer read getCount;
property First: ThingT read getFirst;
property Items[Index: integer]: ThingT read getThing; default;
end;
// error appears on whatever line number comes after the declaration of IThingListOf<ThingT>...end;
function AnythingYouLikeHere:Integer; // there is nothign wrong with this line, but you get the E2134 here.
It appears that the problem is in IThingContainer itself:
IThingContainer = interface ...
...
procedure DoSomething(const Param);
end;
THe above "const Param" has no type information. This is a weird (armpit) of Pascal/Delphi in my opinion, where you completely violate Wirth's idea of strong typing. It is about as weakly typed as a "void *" pointer in C, or the "Pointer" type in Delphi, but it is rarely used, except for in places like the standard pre-object-pascal RTL functions like Move, and so on. In my opinion, untyped parameters in interfaces, used in generics, should either be allowed, or disallowed, but not allowed sometimes, and disallowed other times.
This is a case of a Pascal feature from 1978 mixing badly with an ObjectPascal feature from 2009.
The error message means there's no type info available for the given type.
Here's a minimal program which produces the message:
type
{$M+}
IThing = interface
procedure P(const X);
end;
{$M-}
begin
end.
The problem, it would appear, is that IThingListOf<>, or one of its ancestors, was compiled with {$M+} active. The compiler presumes from this that you really want full type info for the interface; originally it was used by SOAP etc. support to produce stubs etc. The interface RTTI doesn't support untyped parameters (logically enough, they can't be marshalled by SOAP etc.) - and they show up as being of void type, and you end up with this error message.
The solution is to either not use {$M+} - though presumably the RTTI is being used, otherwise it wouldn't be enabled - or use e.g. Pointer instead, and pass the address explicitly.
It's kinda hard to say from this, especially without the definition of IThingContainer available. If you comment the interface definition out, will it compile past that point? Obviously it will break when you try to create a class that implements the interface, but does commenting it out fix this problem?
If so, then the compiler's choking on something in the interface definition. Try commenting out parts of it to figure out where the problem is. If not, then you'll have to look somewhere else.
TMyDataList<T: TBaseDatafile, constructor> = class(TObjectList<TBaseDatafile>)
public
constructor Create;
procedure upload(db: TDataSet);
end;
I read in a blog post (I don't remember where now) that this is the way to declare a generic-based class with a specific base type for the generic object. And the compiler will accept it just fine. But when I try to use it, it decides not to cooperate.
type
TDescendantList = TMyDataList<TDescendantDatafile>;
This gives me a compiler error.
[DCC Error] my_database.pas(1145): E2010 Incompatible types: 'TDescendantDatafile' and 'TBaseDatafile'
Thing is, 1145 isn't even a valid line. The file in question ends at #1142, and the type declaration that it's complaining about is on line #20. This makes me wonder if it's a compiler glitch. Or do I just not quite have the syntax right? Does anyone know a way to make this work?
EDIT: Jim pointed out that it compiles fine when he tried it. A bit more information: I have the base datafile type and the generic list declared in the same unit, while TDescendantDatafile is in a second unit and TDescendantList is defined in a third one. I've already found and reported one bug in D2009's compiler involving generics screwing up types across multiple units. This may be related. Can anyone confirm this?
The definition of TObjectList<> is:
TObjectList<T: class> = class(TList<T>)
So you like to do something like:
TMyDataList<T: TBaseDatafile> = class(TObjectList<T>)
Unfortunately, that won't work. Luckily:
TMyDataList<T: class> = class(TObjectList<T>)
Works, but that is probably not what you want. Because it will not take advantage of the class type. I really think the class specifier is a bit strange here. (TObject should have avoided the problems). But that's no help for you.
Then again, the following works:
TBaseDataFile = class
end;
TDescendantDatafile = class (TBaseDataFile)
end;
TMyDataList<T: TBaseDataFile> = class(TObjectList<TBaseDataFile>)
public
constructor Create;
end;
Are you sure TDescendantDataFile inherits from TBaseDataFile?
In the old days, (read turbo pascal) sometimes the line numbers where wrong because of invisible characters. But I don't think that is still valid.
When TDescendantDatafile descends from TBaseDataFile it works fine on my machine. Check your class hierarchy.
If I change the ancestor of TDescendantDatafile then I get that same error message, and it gives my the right line numbers. If you the compiler is giving you the wrong line numbers then close the project, reopen it and do a full build.