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 !

  • All field names must be distinct - even if they occur in different variants.
  • If a variant is empty (ie, has no fields), the form is: C:()
  • A field list can have only one variant part and it must follow the fixed part of the record.
  • A variant may itself contain a variant part; hence variant parts can be nested.
  • The scope of enumerated type constant identifiers that are introduced in a record type extends over the enclosing block.
  • 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

    上一篇: 在不加载类的情况下近似class.getSimpleName()的最佳方式是什么?

    下一篇: 可以在FreePascal中的变体记录的情况下复制标识符吗?