Recent

Author Topic: Casting to records  (Read 6154 times)

cov

  • Full Member
  • ***
  • Posts: 241
Casting to records
« on: February 13, 2017, 12:25:33 pm »
I have two records defined thus:
Code: Pascal  [Select][+][-]
  1.   T3DPoint=record
  2.     X,Y,Z:Real;
  3.   end;      
  4.  
  5.   T2DPoint=record
  6.     X,Y:Real;
  7.   end;              

I want to convert an instance of T3DPoint to a T2DPoint. I'm not interested in the "Z" value and will discard it.

Can I just cast it as a T2DPoint? Currently I'm using:
Code: Pascal  [Select][+][-]
  1. function convert3Dto2D(pt: T3DPoint):T2DPoint
  2. begin
  3.   Result.X:=pt.X;
  4.   Result.Y:=pt.Y;
  5. end;
« Last Edit: February 13, 2017, 12:28:29 pm by cov »

Eugene Loza

  • Hero Member
  • *****
  • Posts: 663
    • My games in Pascal
Re: Casting to records
« Reply #1 on: February 13, 2017, 12:33:37 pm »
I don't think you can typecast (safely) records even in advanced records mode as they don't have inheritance, afaik.
You can do manual conversion as you already do it.
You can declare records as classes with inheritance. However, this will require you to "create" the classes each time.
Code: Pascal  [Select][+][-]
  1. T2DPoint = class
  2.   x,y: Real;
  3. end;
  4.  
  5. T3DPoint = class(T2DPoint)
  6.   z: Real;
  7. end;
You can have sub-records. But that'll force you to use ugly record1.record2.value syntax.
Code: Pascal  [Select][+][-]
  1. T3DPoint = record
  2.  xy: T2DPoint;
  3.   z: Real;
  4. end;
Or you can just "forget about it" and use only T3DPoint, with flag "is2d: boolean" which if true will force T3DPoint processing as a 2Dpoint.
My FOSS games in FreePascal&CastleGameEngine: https://decoherence.itch.io/ (Sources: https://gitlab.com/EugeneLoza)

cov

  • Full Member
  • ***
  • Posts: 241
Re: Casting to records
« Reply #2 on: February 13, 2017, 01:09:23 pm »
Ok, thanks.

I'll just keep on doing what I'm doing then.  :)

Cyrax

  • Hero Member
  • *****
  • Posts: 836
Re: Casting to records
« Reply #3 on: February 13, 2017, 01:22:04 pm »
You can use this
Code: Pascal  [Select][+][-]
  1. Type
  2.   P3DPoint = ^T3DPoint;
  3.   T3DPoint = packed record
  4.     X,Y,Z:Real;
  5.   end;      
  6.  
  7.   P2DPoint = ^T2DPoint;
  8.   T2DPoint =packed record
  9.     X,Y:Real;
  10.   end;
  11.  
  12. Var
  13.   A3DPoint : P3DPoint;
  14.   A2DPoint : P2DPoint;
  15.   B3DPoint : T3DPoint;
  16.   B2DPoint : T2DPoint;
  17. begin
  18.   FillChar(B2DPoint, SizeOf(T2DPoint), 0);
  19.   A2DPoint := @B2DPoint;
  20.   A3DPoint := @B3DPoint ;
  21.   B3DPoint.X := 1.0;
  22.   B3DPoint.Y := 2.0;
  23.   B3DPoint.Z := 3.0;
  24.   Move(A3DPoint^, A2DPoint^, SizeOf(Real) * 2);
  25. end;
  26.  

Or this

Code: Pascal  [Select][+][-]
  1. Type
  2.   P3DPoint = ^T3DPoint;
  3.   T3DPoint = packed record
  4.     X,Y,Z:Real;
  5.   end;      
  6.  
  7.   P2DPoint = ^T2DPoint;
  8.   T2DPoint =packed record
  9.     X,Y:Real;
  10.   end;
  11.  
  12. Var
  13.   A2DPoint : P2DPoint;
  14.   B3DPoint : T3DPoint;
  15. begin
  16.   B3DPoint.X := 1.0;
  17.   B3DPoint.Y := 2.0;
  18.   B3DPoint.Z := 3.0;
  19.   A2DPoint := @B3DPoint;  
  20. end;
  21.  

DISCLAIMER : Untested. Poster of this code is not responsible for any harm that it might cause for your health (mind and/or other).

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Casting to records
« Reply #4 on: February 13, 2017, 01:39:44 pm »
You can also do this
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$ModeSwitch advancedrecords}
  4.  
  5. type
  6.  
  7.   T2D3DPoint = record
  8.     Is3D: boolean;
  9.     case boolean of
  10.       True: (X3d, Y3d, Z3d: double);
  11.       False: (X2d, Y2d: double);
  12.   end;
  13.  
  14.   T2D3DRec = record
  15.   private
  16.     F2D3DPoint: T2D3DPoint;
  17.   public
  18.     procedure Init(Is3D: boolean; X, Y, Z: double);
  19.     procedure ShowValues;
  20.     procedure Set3D(a3D: boolean=True);
  21.   end;
  22.  
  23. procedure T2D3DRec.Init(Is3D: boolean; X, Y, Z: double);
  24. begin
  25.   F2D3DPoint.Is3D:=Is3D;
  26.   F2D3DPoint.X2d:=X;
  27.   F2D3DPoint.Y2d:=Y;
  28.   if Is3D then
  29.     F2D3DPoint.Z3d:=Z;
  30. end;
  31.  
  32. procedure T2D3DRec.ShowValues;
  33. begin
  34.   case F2D3DPoint.Is3D of
  35.     True: WriteLn('3D: X=',F2D3DPoint.X3d:3:2,', Y=',F2D3DPoint.Y3d:3:2,', Z=',F2D3DPoint.Z3d:3:2);
  36.     False: WriteLn('2D: X=',F2D3DPoint.X2d:3:2,', Y=',F2D3DPoint.Y2d:3:2);
  37.   end;
  38. end;
  39.  
  40. procedure T2D3DRec.Set3D(a3D: boolean);
  41. begin
  42.   F2D3DPoint.Is3D:=a3D;
  43. end;
  44.  
  45. var
  46.   a2d, a3d: T2D3DRec;
  47.  
  48. begin
  49.   a3d:=default(T2D3DRec);
  50.   a3d.Init(True,1.23,4.56,7.89);
  51.   a3d.ShowValues;
  52.   a2d:=a3d;
  53.   a2d.Set3D(False);
  54.   a2d.ShowValues;
  55. end.

derek.john.evans

  • Guest
Re: Casting to records
« Reply #5 on: February 13, 2017, 02:02:53 pm »
Objects?
Code: Pascal  [Select][+][-]
  1. type
  2.   TSingle2 = object
  3.     X, Y: single;
  4.   end;
  5.  
  6.   TSingle3 = object(TSingle2)
  7.     Z: single;
  8.   end;
  9.  
  10. function DotProduct(const A, B: TSingle2): single;
  11. begin
  12.   Result := (A.X * B.X) + (A.Y * B.Y);
  13. end;
  14.  
  15. function DotProduct(const A, B: TSingle3): single;
  16. begin
  17.   Result := (A.X * B.X) + (A.Y * B.Y) + (A.Z * B.Z);
  18. end;  
  19.  

These work without casting:
Code: Pascal  [Select][+][-]
  1. var
  2.   V2: TSingle2;
  3.   V3: TSingle3;
  4. begin
  5.   V2 := V3;
  6.   DotProduct(V2, V3);
  7.   DotProduct(V2, V3);
  8.   DotProduct(V3, V3);  
  9.  

Bart

  • Hero Member
  • *****
  • Posts: 5275
    • Bart en Mariska's Webstek
Re: Casting to records
« Reply #6 on: February 13, 2017, 02:12:10 pm »
Your original code is correct and safe and the way to go.
(It is not casting however.)

Bart

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Casting to records
« Reply #7 on: February 13, 2017, 02:16:38 pm »
You mean like Types.Tpointf.dotproduct ?

We do needs a TPoint3F though :-)

http://docwiki.embarcadero.com/Libraries/XE5/en/System.Types.TPoint3D

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: Casting to records
« Reply #8 on: February 13, 2017, 02:30:18 pm »
I don't think you can typecast (safely) records even in advanced records mode as they don't have inheritance, afaik.
That's noise. Has nothing to do with inheritance. Has nothing to do with advanced records. At All. Why do you write such things? Highly misleading.
It has only to do with the memory layout of a record.
As Bart says, the original code is correct: given  a Record with 3 elements, you can cast it to a record that exists of just the first two elements as long as the alignment is the same for both record types.

It is a candidate for a variant record, though. See the code from howardpc.

One remark: don't use real. Although it works, real is just an alias for double in modern Pascal.
« Last Edit: February 13, 2017, 02:36:28 pm by Thaddy »
Specialize a type, not a var.

Lupp

  • New Member
  • *
  • Posts: 31
Re: Casting to records
« Reply #9 on: February 13, 2017, 03:19:05 pm »
I am not doubting the statements by Bart and by Thaddy. In specific I am aware of the relevance of alignment an types of the record fields.
However, as one having paused many years with actual progranmming, I am curious about the question if (or under what conditions) Pascal can assure us that the order and alignment of fields is as needed for the specific example.

I would be glad to get comments on the question if
Code: Pascal  [Select][+][-]
  1. function MoveNosePart(var p1: T3DPoint; var p2: T2DPoint): Boolean;
  2.   var h: T2DPoint absolute p1;
  3.   begin
  4.   Result := SizeOf(T2DPoint) <= SizeOf(T3DPoint);
  5.   if Result then
  6.     p2 := h;
  7.   end;  
can be used as a kind of template for similar tasks with larger record types starting with equally sized fields in respective order.
I feel that otherwise absolute declarations would have been eliminated from the language.
(The function worked for me when -superficially- tested with the original types.)
« Last Edit: February 13, 2017, 03:22:29 pm by Lupp »

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: Casting to records
« Reply #10 on: February 13, 2017, 03:26:10 pm »
If there are alignment issues, absolute will have  the same alignment issues and more side effects when the alignment is not byte aligned or natural alignment.
But you can always use packed records,or adjust the alignment with the align directive.

The cast is pretty safe in this case, as long as both record types are compiled with the same alignment.
If you want truly predictable alignment, use packed records.
Code: Pascal  [Select][+][-]
  1. type
  2.   T2dPoint = packed record
  3. ..
  4.  
To complicate matters you can use {$packrecords 1/2/4/8/C} or {$a1/2/4/8} but note that that will pack just all records that are NOT declared as packed to the specified alignment.
A record declared specifically as packed will always be in $A1 alignment, i.e. byte aligned. Has to do with File IO reasons: you don't want to store and read slackspace.
See http://freepascal.org/docs-html/current/prog/progsu60.html#x67-660001.2.60 but that is not very clear about the above.

Predictable consistency is only achieved by a declaration of a packed record which is always byte aligned, whatever the {$packrecords} setting may be.

Demo:
Code: Pascal  [Select][+][-]
  1. program packdemo;
  2. {$ifdef fpc}{$mode delphi}{$H+}{$endif}
  3. {$packrecords 2}
  4. type
  5.   Record_a = packed record
  6.   a:byte;
  7.   b:integer;
  8.   end;
  9.  
  10.   Record_b = record
  11.   a:byte;
  12.   b:integer;
  13.   end;
  14.  
  15. begin
  16.   writeln(sizeOf(Record_a));//should be 5, byte aligned, ignores packrecords
  17.   writeln(sizeOf(Record_b));//should be 6, word aligned, adheres to packrecords
  18. end.
  19.  

Note that absolute has its own uses, many of which are life savers..
One example is that FPC since 3.0 ignores the bit pattern of an integer when cast to a single, but returns the closest single value... (imo stupidest thing ever that the devs decided for 3.0, breaks loads of C translated code) http://wiki.freepascal.org/User_Changes_3.0#Casting_integer_variables_to_floating_point
If you want C like behavior you can declare an single variable as absolute to the integer variable to obtain its bit pattern representation,

For absolute to work on a record that is not packed, you will need to know its alignment beforehand, as the above demo also implies.

Also note that many of the most experienced programmers do not realize the above matters about packed....
« Last Edit: February 13, 2017, 04:35:42 pm by Thaddy »
Specialize a type, not a var.

Lupp

  • New Member
  • *
  • Posts: 31
Re: Casting to records
« Reply #11 on: February 13, 2017, 06:17:36 pm »
Quote from: Thaddy
...you can declare a single variable as absolute to the integer variable to obtain its bit pattern representation...
Doing like this was the example I started with answering in this forum a few days ago (about conversion into "binary"). It is also concerning things I did many (about 30) years ago and am considering to touch again.
(I was a kind of a programmer for a very short time in the early days when the field was open to everybody and expertise was rare. This was in the 1960es.)

User137

  • Hero Member
  • *****
  • Posts: 1791
    • Nxpascal home
Re: Casting to records
« Reply #12 on: February 14, 2017, 12:40:06 am »
Operator overloading wasn't mentioned yet
Code: Pascal  [Select][+][-]
  1. interface
  2. ...
  3. type
  4.   T3DPoint=record
  5.     X,Y,Z:Real;
  6.   end;
  7.   T2DPoint=record
  8.     X,Y:Real;
  9.   end;
  10. ...
  11. operator:=(const p: T3DPoint): T2DPoint;
  12. operator:=(const p: T2DPoint): T3DPoint;
  13.  
  14. implementation
  15.  
  16. operator:=(const p: T3DPoint): T2DPoint;
  17. begin
  18.   result.X:=p.X;
  19.   result.Y:=p.Y;
  20. end;
  21.  
  22. operator:=(const p: T2DPoint): T3DPoint;
  23. begin
  24.   result.X:=p.X;
  25.   result.Y:=p.Y;
  26.   result.Z:=0;
  27. end;
  28.  
  29. procedure TForm1.FormCreate(Sender: TObject);
  30. var p2d: T2DPoint;
  31.     p3d: T3DPoint;
  32. begin
  33.   p2d:=p3d; // Both ways assignment
  34.   p3d:=p2d;
  35. end;

 

TinyPortal © 2005-2018