Can Identifiers be duplicated across cases of a variant record in FreePascal?
Here's my problem: I want to create a record type where among the cases of a variant record, some, but not all, will have a certain field. According to the wiki, this is perfectly 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.
the compilation fails 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:lazarusfpc3.0.0binx86_64-win64ppcx64.exe returned an error exitcode
Is the wiki simply wrong, or am I missing something here? Is there any way to achieve this effect I want?
Very interesting question. I was sure this is possible. If You modify Your code to:
..
married, widowed, divorced: (marriageDate: TDateTime);
divorced: (divorceDate: TDateTime; isFirstDivorce: boolean)
..
it works, but it is not the result you intend to have. Since marriageDate and the divorceDate overlay each other (as mentioned in the comments!)
This picture is taken from "Pascal users manual (4th edition)" and as you can see the variant parts have the same memory location.
According to Pascal users manual (4th edition) and to the book "Turbo Pascal ISBN 3-89011-060-6 the described record declaration on your quoted wiki is not valid !
Point 1 is the relevant one here! The book "Turbo Pascal" the suggested solution is to use a unique prefix for field names that occur multiple times.
In Your case Your could would look like:
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;
The other solution would be to define married, devorced ... as a record type.
..
married : (m: TMarried);
divorced : (d: TDivorced);
..
This 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 could also define the records inline but the above is clearer, I think. Here's the 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.
What Baltasar proposes will compile, but not do what you want. marriageDate
and divorceDate
will overlap and writing to one of them will also modify the other, since they are simply at the same address.
But in this case, there is no good reason for a variant record at all.
Why not simply:
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;
The usage and layout is exactly what you need. If a field does not apply (eg marriageDate
for a single
, or divorceDate
for a married
), you simply don't use it.
That is the same as with a variant record. There you also only set the fields that apply. Note that the compiler or runtime do not prevent you from writing to the wrong field of a variant record anyway, ie in a variant record, if the status is single
, you can still write to or read from divorceDate
, even if that makes no sense.
If you want to distinguish several different setups, simply do that in comments, and forget the variant record, you don't 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 absolutely no need to make this record variant,
I will post my Project62.dpr, which shows exactly the same offsets for 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's 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
The layout in 32 bit can be put in a simple diagram like so:
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]
链接地址: http://www.djcxy.com/p/91404.html