Recent

Author Topic: Can I override a class var or const somehow?  (Read 6892 times)

ahydra

  • New Member
  • *
  • Posts: 19
Can I override a class var or const somehow?
« on: February 03, 2018, 07:51:47 am »
I'm trying to assign a unique ID (user-friendly, not just the VMT pointer) to a set of classes all of which inherit from some base type. As an example:

Code: Pascal  [Select][+][-]
  1. type
  2.   TA = class // base type
  3.     class const
  4.       CODE: integer = 0; // sentinel for checking the descendant does actually override this
  5.   end;
  6.  
  7.   TB = class(TA)
  8.     class const
  9.       CODE: integer = 1;
  10.   end;
  11.  
  12.   TC = class(TA)
  13.     class const
  14.       CODE: integer = 2;
  15.   end;
  16.  
  17.   TAClass = class of TA;
  18.  
  19. var
  20.   C: TAClass;
  21.  
  22. begin
  23.   C := TB; // or TC
  24.   writeln(C.CODE);
  25. end.
  26.  

This doesn't compile (redefinition of CODE), likewise using class vars doesn't work for the same reason, and if you put just one class var in the base (TA) type, it will be common to all descendant types.

I've currently solved this by using a virtual class function to return the CODE, but that has performance impact - since the value is referenced a lot, this class function actually showed up as like the 6th most time-consuming function in my (very complex) project when using perf record / perf report. Simply deref'ing some offset from the VMT is much faster than a function call.

What's the best solution here?

Thanks in advance,

ahydra

Thaddy

  • Hero Member
  • *****
  • Posts: 14169
  • Probably until I exterminate Putin.
Re: Can I override a class var or const somehow?
« Reply #1 on: February 03, 2018, 08:43:40 am »
Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2. type
  3.   TA = class // base type
  4.     class var
  5.       CODE: integer; // sentinel for checking the descendant does actually override this
  6.     class constructor create;  
  7.   end;
  8.  
  9.   TB = class(TA)
  10.     class var
  11.       CODE: integer;
  12.     class constructor create;  
  13.   end;
  14.  
  15.   TC = class(TA)
  16.     class var
  17.       CODE: integer;
  18.     class constructor create;  
  19.   end;
  20.      
  21.   class constructor TA.Create;
  22.   begin
  23.     CODE := 0;
  24.   end;
  25.  
  26.   class constructor TB.Create;
  27.   begin
  28.     CODE := 1;
  29.   end;
  30.  
  31.   class constructor TC.Create;
  32.   begin
  33.     CODE := 2;
  34.   end;
  35.  
  36. begin
  37.  writeln(Ta.Code:3,Tb.Code:3,Tc.code:3);  // outputs 0,1,2
  38. end.

Quote
This doesn't compile (redefinition of CODE), likewise using class vars doesn't work for the same reason, and if you put just one class var in the base (TA) type, it will be common to all descendant types.
You are wrong, here....:
A class var - or a class property for that matter - is associated to the particular class in which it is defined, but not to descendant classes.
See also the reference guide:
https://www.freepascal.org/docs-html/ref/refse40.html
https://www.freepascal.org/docs-html/ref/refsu24.html#x72-940006.2.2
Whereas a class const - which is simply const! - is associated to the complete inheritance tree from the point it is defined.
That's the reason your code did not work and my example does.

Probably the only reason your code for class vars did not work is because it does not accept a default value: you need to set the value in the class constructor which is called/resolved automatically.

For completeness:
If you need an inherited value write something like:
Code: Pascal  [Select][+][-]
  1.   class constructor TC.Create;
  2.   begin
  3.     CODE := Ta.code;
  4.   end;

Quote
Simply deref'ing some offset from the VMT is much faster than a function call.
Then declare the function inline. That does exactly that!
« Last Edit: February 03, 2018, 12:40:35 pm by Thaddy »
Specialize a type, not a var.

Thaddy

  • Hero Member
  • *****
  • Posts: 14169
  • Probably until I exterminate Putin.
Re: Can I override a class var or const somehow?
« Reply #2 on: February 03, 2018, 12:35:55 pm »
Some proof, note a class const=const. Nothing more.
Code: Pascal  [Select][+][-]
  1. {$mode delphi}{$J+}
  2. type
  3.   TA = class // base type
  4.     const CODE: integer = 0;
  5.     class constructor create;  
  6.   end;
  7.  
  8.   TB = class(TA)
  9.     class constructor create;  
  10.   end;
  11.  
  12.   TC = class(TA)
  13.     class constructor create;  
  14.   end;
  15.      
  16.   class constructor TA.Create;
  17.   begin
  18.     CODE := 0;
  19.   end;
  20.  
  21.   class constructor TB.Create;
  22.   begin
  23.     CODE := 1;
  24.   end;
  25.  
  26.   class constructor TC.Create;
  27.   begin
  28.     CODE := 2;
  29.   end;
  30.  
  31. begin
  32.  writeln(Ta.Code:2,Tb.Code:2,Tc.code:2); // will output 2,2,2, which is correct.
  33. end.

Since we changed the const, it is changed everywhere, as opposed to the first example. And this is by design!
(A const is const, even with a class)

So I suppose you will need something like my first example, not this one  :P
« Last Edit: February 03, 2018, 12:56:07 pm by Thaddy »
Specialize a type, not a var.

Ñuño_Martínez

  • Hero Member
  • *****
  • Posts: 1186
    • Burdjia
Re: Can I override a class var or const somehow?
« Reply #3 on: February 03, 2018, 01:30:48 pm »
I think you can use properties for that.
Code: Pascal  [Select][+][-]
  1. TYPE
  2.   TypeA = CLASS (TObject)
  3.   PRIVATE
  4.     fValue: INTEGER;
  5.   PROTECTED
  6.     FUNCTION GetValue: INTEGER; VIRTUAL;
  7.   PUBLIC
  8.     PROPERTY TheConstValue: INTEGER READ GetValue;
  9.   END;
  10.  
  11.   TypeB = CLASS (TypeA)
  12.   PRIVATE
  13.     fValue: INTEGER;
  14.   PROTECTED
  15.     FUNCTION GetValue: INTEGER; OVERRIDE;
  16.   END;
  17.  
  18.  
  19.   FUNCTION TypeA.GetValue: INTEGER; BEGIN EXIT (fValue) END;
  20.  
  21.   FUNCTION TypeB.GetValue: INTEGER; BEGIN EXIT (fValue) END;
  22.  
It will use different "fields" in each class.
Are you interested in game programming? Join the Pascal Game Development community!
Also visit the Game Development Portal

Thaddy

  • Hero Member
  • *****
  • Posts: 14169
  • Probably until I exterminate Putin.
Re: Can I override a class var or const somehow?
« Reply #4 on: February 03, 2018, 03:46:08 pm »
That's what I wrote. And already demonstrated. And you don't have to think: it is in the manuals! See first link.
« Last Edit: February 03, 2018, 04:20:20 pm by Thaddy »
Specialize a type, not a var.

Kays

  • Hero Member
  • *****
  • Posts: 569
  • Whasup!?
    • KaiBurghardt.de
Re: Can I override a class var or const somehow?
« Reply #5 on: February 03, 2018, 08:45:00 pm »
[…] assign a unique ID […] to a set of classes all of which inherit from some base type. […]
And typeInfo? typeInfo is a compiler function and returns a PTypeInfo. So, it basically results in a mov-instruction, thus virtually has the same, if not less, overhead than introducing and maintaining an extra symbol.

Yours Sincerely
Kai Burghardt

ahydra

  • New Member
  • *
  • Posts: 19
Re: Can I override a class var or const somehow?
« Reply #6 on: February 04, 2018, 08:39:10 am »
@Thaddy

Sorry, but it appears I was right. This doesn't compile (which is what I was referring to when I said class vars don't work "for the same reason" [as class consts]):

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. type
  4.  
  5.   TA = class
  6.     class var
  7.       code: integer;
  8.  
  9.     class constructor Create();
  10.   end;
  11.  
  12.   TB = class(TA)
  13.     class var
  14.       code: integer; // compile error!
  15.  
  16.     class constructor Create();
  17.   end;
  18.  
  19.   TAClass = class of TA;
  20.  
  21. var
  22.   ac: TAClass;
  23.  
  24. class constructor TA.Create();
  25.   begin
  26.     code := 0;
  27. end;
  28.  
  29. class constructor TB.Create();
  30.   begin
  31.     code := 1;
  32. end;
  33.  
  34. begin
  35.   ac := TB;
  36.   writeln(ac.code);
  37. end.
  38.  

Compiler gives a "duplicate identifier" error on the line defining TB.code.

If I add {$mode delphi}, it will now compile, but prints 0 instead of the expected 1. (It also says ac is assigned but not used, when it clearly is.) And the rest of the project is not using {$mode delphi}, so this isn't really an option anyway.

I am familiar with the difference between class fields and instance fields, don't worry :). But it appears that unless we've missed something, FPC is simply lacking the ability to do what I need here.

Inlining the class function as you also suggested is a possibility, but is inline inherited, i.e. if I mark class function TA.GetCode() inline but not TB's override of it, will TB's function be inline too? The problem here is that my project is a framework on which you build applications, so it's rather messy to have to mandate that people add "inline" to their function definition.

I am also considering just making the "code" a regular field (instance field), defined in the base class only, and changing the API that is used to define the list of all descendant types so that you pass the code in along with the type VMT and any new instances that are created are automatically assigned the correct code. Fiddly, but workable if no other solution can be found.

Thanks,

ahydra

Thaddy

  • Hero Member
  • *****
  • Posts: 14169
  • Probably until I exterminate Putin.
Re: Can I override a class var or const somehow?
« Reply #7 on: February 04, 2018, 08:58:41 am »
 What you want is my first example. Copy it exactly. That code is bug free. The mode does not matter as lomng as it is Delphi or ObjFpc, my code also compiles in {$mode objfpc} without any error hint or warning.
Are you using a modern FPC 3.0.4? If not, upgrade.

Note you can not assign a class of type TB to a class reference of type TA. That's programmer error. Of course it prints zero: it becomes a reference to TA, not TB.
And you don need it. What the compiler told you is true:"Error: Incompatible types: got "Class Of TB" expected "TA" Both need to be class references: with a TBClass the code compiles, but it will still print zero, because you ask for TA, NOT TB. Again, do not use class references here.... And remember that a class var is (with the same effect as my second example - really not inheritable: Well, it is but not if you reintroduce it. It is local to a particular class. What you want is an identifier that distinguishes a particular class in the same inheritance tree in a friendly manner, for that you can use a class var.
But for your purpose you must be able to use my first example. Also look at advanced RTTI, Classtype and ClassParent, that gives you a bit more flexibility.
Note if you override an inlined method you still need to add inline... That's because inline is not part of the method signature, but a compiler hint. I have some thoughts about how to solve it cleanly, will add it later.
« Last Edit: February 04, 2018, 09:55:00 am by Thaddy »
Specialize a type, not a var.

Thaddy

  • Hero Member
  • *****
  • Posts: 14169
  • Probably until I exterminate Putin.
Re: Can I override a class var or const somehow?
« Reply #8 on: February 04, 2018, 11:22:56 am »
Anyway: can you use interfaces? That would solve the problem in a clean way too.
Specialize a type, not a var.

hnb

  • Sr. Member
  • ****
  • Posts: 270
Re: Can I override a class var or const somehow?
« Reply #9 on: February 04, 2018, 01:23:34 pm »
You can't override class var nor consts inside classes. The only workaround for this is virtual class methods usage, but this technique has some limits. In my TODO is new kind of data for classes:

Code: [Select]
type
  TFooClass = class of TFooA;
  TFooA = class
    class const
      FOO: string = 'something';
  end;

  TFooB = class(TFooA)
    class const
      FOO: string = 'something new'; // implicit override
  end;

var
  fc: TFooClass;
begin
  fc := TFooA;
  WriteLn(fc.FOO); // print something
  fc := TFooB;
  WriteLn(fc.FOO); // print something new
end.

such feature needs new VMT entry. Anyway I have bigger priority for other features so I have no idea when such feature will be released. For now you can use hack for VMT to achieve similar feature with current version of FPC: for example here is on of hacks programmed by BeRo (Benjamin Rosseaux):

https://gist.github.com/BeRo1985/d5613b7b30b637fc8ee307ef9e8a9b7a

good luck!
Checkout NewPascal initiative and donate beer - ready to use tuned FPC compiler + Lazarus for mORMot project

best regards,
Maciej Izak

Thaddy

  • Hero Member
  • *****
  • Posts: 14169
  • Probably until I exterminate Putin.
Re: Can I override a class var or const somehow?
« Reply #10 on: February 04, 2018, 02:08:37 pm »
 :D :D :D :D :D
When do we have default?  :D :D :D
Anyway.

My other solution was an internal record. Which seems to work.
« Last Edit: February 04, 2018, 02:13:12 pm by Thaddy »
Specialize a type, not a var.

ahydra

  • New Member
  • *
  • Posts: 19
Re: Can I override a class var or const somehow?
« Reply #11 on: February 05, 2018, 03:06:10 am »
What you want is my first example. Copy it exactly. That code is bug free. The mode does not matter as lomng as it is Delphi or ObjFpc, my code also compiles in {$mode objfpc} without any error hint or warning.
Are you using a modern FPC 3.0.4? If not, upgrade.

Yes, I am using FPC 3.0.4 (svn r56594). It does not compile, I promise :). I took a New Project -> Program, pasted your code in after the "uses", and got the following errors:

Free Pascal Compiler version 3.0.4 [2017/12/03] for i386
...
project1.lpr(21,7) Error: (5002) Duplicate identifier "CODE"
project1.lpr(21,7) Hint: (5003) Identifier already defined in unit PROJECT1 at line 15
project1.lpr(27,7) Error: (5002) Duplicate identifier "CODE"
project1.lpr(27,7) Hint: (5003) Identifier already defined in unit PROJECT1 at line 15
...

Quote
Note you can not assign a class of type TB to a class reference of type TA. That's programmer error. Of course it prints zero: it becomes a reference to TA, not TB.
And you don need it. What the compiler told you is true:"Error: Incompatible types: got "Class Of TB" expected "TA" Both need to be class references: with a TBClass the code compiles, but it will still print zero, because you ask for TA, NOT TB. Again, do not use class references here....

Sorry but I have no clue what you're going on about here. "class of TA" is a class reference / metaclass (implemented as a VMT pointer). Something of this type can be assigned any VMT pointer that is either TA itself or inherits from TA, and can be used to dynamically create classes of different types, as per https://www.freepascal.org/docs-html/ref/refse34.html. Here TB inherits from TA, so it's fine to assign ac := TB.

I do need this functionality in order to pass the list of class definitions into the framework and then have the framework spit out objects of a requested type, as in the doc example. It appears this is just broken or behaves completely differently in {$mode delphi}. Perhaps the misunderstanding here is simply that you're doing everything in {$mode delphi} and I'm using {$mode objfpc} ?

@hnb: sounds promising, I look forward to the new function. I considered hacking the VMT in similar ways to the link you provided, but it seems really ugly, particularly as you first have to first convince the OS to let you edit it. (How would you even free the GetMem'd memory off in the example given there? I guess you'd have to register it in some store and iterate through the allocated blocks in the finalization section.)

ahydra

 

TinyPortal © 2005-2018