Prima di tutto, qualche consiglio generale. Il tuo SSCCE è povero. Non è né corto né autonomo. Questo è in realtà piuttosto importante. Rendere il codice dimostrativo il più breve possibile aiuta spesso a capire il problema. Questo è sicuramente il caso qui.
Ecco il mio prendere su uno SSCCE:
program soq19147523_version1;
type
TRecord = record
data: Integer;
proc: procedure(const rec: TRecord);
end;
procedure myproc(const rec: TRecord);
begin
end;
procedure foo;
var
rec: TRecord;
begin
rec.proc := myproc; // fail, E2009
end;
begin
end.
Questo non riesce a compilare con E2009. Puoi farlo compilare in diversi modi. Ad esempio, la rimozione dei risultati del membro data
nella compilazione riuscita.
program soq19147523_version2;
type
TRecord = record
proc: procedure(const rec: TRecord);
end;
procedure myproc(const rec: TRecord);
begin
end;
procedure foo;
var
rec: TRecord;
begin
rec.proc := myproc; // compiles
end;
begin
end.
In XE3 si può fare compilare aggiungendo l'attributo [ref]
al parametro di tipo procedurale. Per essere espliciti, questo compila in XE3:
program soq19147523_version3;
type
TRecord = record
data: Integer;
proc: procedure(const [ref] rec: TRecord);
end;
procedure myproc(const [ref] rec: TRecord);
begin
end;
procedure foo;
var
rec: TRecord;
begin
rec.proc := myproc; // compiles in XE3, no [ref] in XE2
end;
begin
end.
Questo ci dà un forte indizio su ciò che il compilatore sta facendo. Un parametro record const
non decorato viene passato per valore o per riferimento. Se il record è abbastanza piccolo da inserirsi in un registro, verrà passato per valore.
Quando il compilatore sta elaborando il record, non ha completato completamente le dimensioni del record. Immagino che internamente al compilatore ci sia una variabile contenente la dimensione del record. Finché la dichiarazione del record non è completa, suppongo che questa variabile di dimensione sia zero. Quindi il compilatore decide che il parametro const
del tipo di record verrà passato in base al valore in un registro. Quando viene rilevata la procedura myproc
, è nota la dimensione reale del record. Non si adatta a un registro e quindi il compilatore riconosce una discrepanza. Il tipo nel record riceve il suo parametro in base al valore, ma quello che viene offerto per l'assegnazione passa il parametro per riferimento.
In effetti, è possibile rimuovere [ref]
dalla dichiarazione di myproc
e il programma si compila ancora.
Questo spiega anche perché è stato rilevato che l'utilizzo di un parametro var
ha comportato una compilazione corretta. Ciò ovviamente costringe il parametro ad essere passato per riferimento.
Se è possibile passare a XE3 o versione successiva, la soluzione è ovvia: utilizzare [ref]
per forzare la mano del compilatore.
Se non è possibile passare a XE3, forse un parametro non codificato const
rappresenta la soluzione migliore. Questo costringe anche il compilatore a passare il parametro per riferimento.
program soq19147523_version4;
type
TRecord = record
data: Integer;
proc: procedure(const rec{: TRecord});
end;
procedure myproc(const rec{: TRecord});
begin
Writeln(TRecord(rec).data);
end;
procedure foo;
var
rec: TRecord;
begin
rec.proc := myproc;
end;
begin
end.
lettori abituali dei miei post qui su Stack Overflow sapranno che io sono un grande sostenitore di overloading degli operatori sui record tipo di valore. Uso questa funzionalità estesamente e risulta in un codice efficiente e altamente leggibile. Tuttavia, quando si inizia a spingere forte con tipi più complessi e interdipendenti, la progettazione e l'implementazione si rompono.
Il difetto evidenziato in questa domanda è un buon esempio. Non è inusuale aspettarsi che il compilatore possa gestirlo. È molto ragionevole aspettarsi che un tipo possa riferirsi a se stesso.
Un altro esempio in cui l'implementazione consente al programmatore di scendere quando si desidera inserire un const
del tipo di record in tale record. Ad esempio, si consideri questo tipo:
type
TComplex = record
public
R, I: Double;
const
Zero: TComplex = (R: 0.0, I: 0.0);
end;
Questo non riesce a compilare alla dichiarazione di Zero
con E2086 Tipo 'TComplex' non è ancora completamente definito.
Un'altra limitazione è l'incapacità del tipo A di fare riferimento al tipo B e viceversa. Possiamo fare dichiarazioni in avanti per le classi, ma non per le registrazioni. Capisco che l'implementazione del compilatore avrebbe bisogno di essere modificata per supportare questo, ma è certamente possibile ottenere.
E c'è di più. Perché non è possibile consentire l'ereditarietà per i record? Non voglio il polimorfismo, voglio solo ereditare i membri dei dati e i metodi del record. E non ho nemmeno bisogno che lo sia un comportamento che si ottiene con le classi. Cioè non mi importa se TDerivedRecord
non è un TBaseRecord
. Tutto ciò che voglio è ereditare membri e funzioni per evitare la duplicazione.
Tristemente, a mio parere, questa è una funzionalità che è stata eseguita al 90% e manca semplicemente la tenera, amorevole cura necessaria per portarla a completamento.
Funziona bene per me quando lo provo. Senza vedere un [SSCCE] (http: // sscce.org), suppongo che ci siano probabilmente più tipi 'TFastDiv' in ambito che sono in conflitto tra loro. –
No, c'è solo sulla definizione del record TFastDiv (confermata con find in files). – Johan
@RemyLebeau, ho aggiunto un SSCCE – Johan