Is it possible to duplicate identifiers in the case of a record option in FreePascal?

Here is my problem: I want to create a record type where, among the cases of the record variant, some, but not all, will have a specific field. According to the wiki , this is completely legal. And yet, when I tried to compile the following code:

program example; {$mode objfpc}{$H+} uses sysutils; type maritalStates = (single, married, widowed, divorced); TPerson = record name: record first, middle, last: string; end; sex: (male, female); dob: TDateTime; case maritalStatus: maritalStates of single: ( ); married, widowed: (marriageDate: TDateTime); divorced: (marriageDate, divorceDate: TDateTime; isFirstDivorce: boolean) end; var ExPerson: TPerson; begin ExPerson.name.first := 'John'; ExPerson.name.middle := 'Bob'; ExPerson.name.last := 'Smith'; ExPerson.sex := male; ExPerson.dob := StrToDate('05/05/1990'); ExPerson.maritalStatus := married; ExPerson.marriageDate := StrToDate('04/01/2015'); end. 

compilation failed with the following error:

 $ fpc ex.pas Free Pascal Compiler version 3.0.0 [2016/02/14] for x86_64 Copyright (c) 1993-2015 by Florian Klaempfl and others Target OS: Win64 for x64 Compiling ex.pas ex.pas(19,18) Error: Duplicate identifier "marriageDate" ex.pas(21,3) Error: Duplicate identifier "marriageDate" ex.pas(35,4) Fatal: There were 2 errors compiling module, stopping Fatal: Compilation aborted Error: C:\lazarus\fpc\3.0.0\bin\x86_64-win64\ppcx64.exe returned an error exitcode 

Is the wiki just wrong or am I missing something? Is there any way I can achieve this effect?

+7
record pascal freepascal variant
source share
3 answers

Very interesting question. I was sure that it was possible. If you change your code to:

 .. married, widowed, divorced: (marriageDate: TDateTime); divorced: (divorceDate: TDateTime; isFirstDivorce: boolean) .. 

it works, but this is not the result that you intend to have. Since weddingDate and divorceDate overlap (as pointed out in the comments!)

enter image description here

This image is taken from the Pascal User Guide (4th Edition), and as you can see, parts of the variant have the same memory location.

According to the Pascal user manual (4th edition) and to the book "Turbo Pascal ISBN 3-89011-060-6, the described entry is not valid in your cited wiki !

  • All field names must be different - even if they occur in different ways.
  • If the option is empty (i.e. has no fields), the form: C :()
  • The list of fields can contain only one part of the variant, and it must follow the fixed part of the record.
  • A variant may itself contain a variant part; therefore options can be nested.
  • The scope of identifiers of numbered type constants that are entered into the record type extends to the enclosing block.

Point 1 is important here! In Turbo Pascal, the proposed solution is to use a unique prefix for field names that occur several times.

In your case, yours might look like this:

 TPerson = record name: record first, middle, last: string; end; sex: (male, female); dob: TDateTime; case maritalStatus: maritalStates of single: ( ); married, widowed: (marMarriageDate: TDateTime); divorced: (divMarriageDate, divorceDate: TDateTime; isFirstDivorce: boolean) end; 

Another solution would be to define a married, faithful ... as a type of record.

 .. married : (m: TMarried); divorced : (d: TDivorced); .. 
+3
source share

It seems to work

 program example; {$mode objfpc}{$H+} uses sysutils; type TMarried = record marriageDate : TDateTime end; TDivorced = record marriageDate : TDateTime; divorceDate : TDateTime; isFirstDivorce: boolean end; TWidowed = TMarried; maritalStates = (single, married, widowed, divorced); TPerson = record name: record first, middle, last: string; end; sex: (male, female); dob: TDateTime; case maritalStatus: maritalStates of single : (); married : (m: TMarried); widowed : (w: TWidowed); divorced : (d: TDivorced); end; var ExPerson: TPerson; begin with ExPerson do begin name.first := 'John'; name.middle := 'Bob'; name.last := 'Smith'; sex := male; dob := StrToDate('05/05/1990'); maritalStatus := married; m.marriageDate := StrToDate('04/01/2015'); end; end. 

EDIT: you can also define inline entries, but I think the above is clearer. Here's an alternative way:

 program example; {$mode objfpc}{$H+} uses sysutils; type maritalStates = (single, married, widowed, divorced); TPerson = record name: record first, middle, last: string; end; sex: (male, female); dob: TDateTime; case maritalStatus: maritalStates of single : (); married : (m: record marriageDate: TDateTime end); widowed : (w: record marriageDate: TDateTime end); divorced : (d: record marriageDate : TDateTime; divorceDate : TDateTime; isFirstDivorce: boolean end) end; var ExPerson: TPerson; begin with ExPerson do begin name.first := 'John'; name.middle := 'Bob'; name.last := 'Smith'; sex := male; dob := StrToDate('05/05/1990'); maritalStatus := married; m.marriageDate := StrToDate('04/01/2015'); end; end. 
+1
source share

What Baltasar offers will compile, but not do what you want. marriageDate and divorceDate will overlap, and writing to one of them will also change the other, as they simply have the same address.

But in this case, there is no good reason to write the option.

Why not just:

 type maritalStates = (single, married, widowed, divorced); TPerson = record name: record first, middle, last: string; end; sex: (male, female); dob: TDateTime; maritalStatus: maritalStates; // single, married, widowed, divorced marriageDate: TDateTime; // married, widowed, divorced divorceDate : TDateTime; // divorced isFirstDivorce: boolean; // divorced end; 

Use and layout is exactly what you need. If the field is not applicable (for example, marriageDate for single or divorceDate for married ), you simply do not use it.

This is the same as with the recording option. There you also set only the fields that apply. Please note that the compiler or runtime does not prevent you from writing in the wrong field of the recording option in any case, that is, in the recording option, if the status is single , you can still write or read from divorceDate , even if it makes no sense.

If you want to distinguish several different settings, just do it in the comments and forget the recording option, you do not need it here. Now you can do:

 var P: TPerson; begin P.name.first := 'Bob'; P.name.middle := 'The'; P.name.last := 'Builder'; P.sex := male; P.dob := StrToDate('05/05/1980'); P.maritalStatus := divorced; P.marriageDate := StrToDate('04/01/2013'); P.divorceDate := StrToDate('04/02/2016'); P.isFirstDivorce := True; // etc... 

Update

Just to show that there is no need to do this recording option,

I will post my Project62.dpr, which shows exactly the same offsets for the corresponding fields and the same record sizes:

 program Project62; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type maritalStates = (single, married, widowed, divorced); tsex = (male, female); // No variant part PPerson = ^TPerson; TPerson = record name: record first, middle, last: string; end; sex: tsex; dob: TDateTime; maritalStatus: maritalStates; // single, married, widowed, divorced marriageDate: TDateTime; // married, widowed, divorced divorceDate : TDateTime; // divorced isFirstDivorceDate: boolean; // divorced end; // Variant part like tonypdmtr record PPerson2 = ^TPerson2; TPerson2 = record name: record first, middle, last: string; end; sex: tsex; dob: TDateTime; case maritalStatus: maritalStates of single: (); widowed: (w: record marriageDate: TDateTime; end); // overlaps with m.marriageDate and d.marriageDate married: (m: record marriageDate: TDateTime; end); // overlaps with w.marriageDate and d.marriageDate divorced: (d: record marriageDate: TDateTime; // overlaps with w.marriageDate and m.marriageDate divorceDate: TDateTime; // same offset as in my non-variant version isFirstDivorceDate: Boolean // same offset as in my non-variant version end); end; begin try Writeln('TPerson: size = ', Sizeof(TPerson)); Writeln('TPerson.maritalStatus: offset = ', NativeUInt(@PPerson(nil)^.maritalStatus)); Writeln('TPerson.marriageDate: offset = ', NativeUInt(@PPerson(nil)^.marriageDate)); Writeln('TPerson.divorceDate: offset = ', NativeUInt(@PPerson(nil)^.divorceDate)); Writeln('TPerson.isFirstDivorceDate: offset = ', NativeUInt(@PPerson(nil)^.isFirstDivorceDate)); Writeln; Writeln('TPerson2: size = ', Sizeof(TPerson2)); Writeln('TPerson2.maritalStatus: offset = ', NativeUInt(@PPerson2(nil)^.maritalStatus)); Writeln('TPerson2.w.marriageDate: offset = ', NativeUInt(@PPerson2(nil)^.w.marriageDate)); Writeln('TPerson2.m.marriageDate: offset = ', NativeUInt(@PPerson2(nil)^.m.marriageDate)); Writeln('TPerson2.d.marriageDate: offset = ', NativeUInt(@PPerson2(nil)^.d.marriageDate)); Writeln('TPerson2.d.divorceDate: offset = ', NativeUInt(@PPerson2(nil)^.d.divorceDate)); Writeln('TPerson2.d.isFirstDivorceDate: offset = ', NativeUInt(@PPerson2(nil)^.d.isFirstDivorceDate)); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Readln; end. 

Output (on Windows):

 TPerson: size = 56 TPerson.maritalStatus: offset = 24 TPerson.marriageDate: offset = 32 TPerson.divorceDate: offset = 40 TPerson.isFirstDivorceDate: offset = 48 TPerson2: size = 56 TPerson2.maritalStatus: offset = 24 TPerson2.w.marriageDate: offset = 32 TPerson2.m.marriageDate: offset = 32 TPerson2.d.marriageDate: offset = 32 TPerson2.d.divorceDate: offset = 40 TPerson2.d.isFirstDivorceDate: offset = 48 

A 32-bit layout can be placed on a simple diagram:

  00 TPerson: [name.first] TPerson2: [name.first] 04 [name.middle] [name.middle] 08 [name.last] [name.last] 12 [sex] [sex] 16 [dob] [dob] 24 [maritalStatus] [maritalStatus] 32 [marriageDate] [w.marriageDate] [m.marriageDate] [d.marriageDate] 40 [divorceDate] [d.divorceDate] 48 [isFirstDivorceDate] [d.isFirstDivorceDate] 
0
source share

All Articles