Why I can't pass 'Child' class instance when switching parameter type from 'const' to 'var' in 'overloaded' methoduac elevate while using ifileoperation copyitemclient side web serviceTNetSharingManager access violation problemStrange “A component named TFrm1 already exists” errorDelphi Rtti for interfaces in a generic contextHow to export Overload functions from DLL?In Delphi, are parameters evaluated in order when passed into a method?Delphi XE3 Invalid Pointer when trying to free FSQL (TStringList)How to pass method type as parameter?How do I convert an enum to string and back again using RTTI in Delphi
What is the most remote airport from the center of the city it supposedly serves?
Independent, post-Brexit Scotland - would there be a hard border with England?
How wide is a neg symbol, how to get the width for alignment?
Out of scope work duties and resignation
Does a card have a keyword if it has the same effect as said keyword?
Has a commercial or military jet bi-plane ever been manufactured?
How to model the curly cable part of the phone
Manager is threatening to grade me poorly if I don't complete the project
Should I replace my bicycle tires if they have not been inflated in multiple years
Can my company stop me from working overtime?
How long would it take for people to notice a mass disappearance?
I drew a randomly colored grid of points with tikz, how do I force it to remember the first grid from then on?
how to ban all connection to .se and .ru in the hosts.deny-file
Why wasn't the Night King naked in S08E03?
Do Maps have an Reliable Relationship between keySet() order and values() order?
Why do people keep telling me that I am a bad photographer?
Why do money exchangers give different rates to different bills?
Why is Arya visibly scared in the library in S8E3?
Getting a W on your transcript for grad school applications
I need a disease
Are there any Final Fantasy Spirits in Super Smash Bros Ultimate?
What does this colon mean? It is not labeling, it is not ternary operator
Why is B♯ higher than C♭ in 31-ET?
Where can I go to avoid planes overhead?
Why I can't pass 'Child' class instance when switching parameter type from 'const' to 'var' in 'overloaded' method
uac elevate while using ifileoperation copyitemclient side web serviceTNetSharingManager access violation problemStrange “A component named TFrm1 already exists” errorDelphi Rtti for interfaces in a generic contextHow to export Overload functions from DLL?In Delphi, are parameters evaluated in order when passed into a method?Delphi XE3 Invalid Pointer when trying to free FSQL (TStringList)How to pass method type as parameter?How do I convert an enum to string and back again using RTTI in Delphi
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;
MCVE:
The following code does not compile with error when switching the parameter type from const
to var
or out
in the overloaded method Train
of the class TAnimalTrainer
but it compiles if non is specified.
[dcc32 Error] Project14.dpr(41): E2250 There is no overloaded version
of 'Train' that can be called with these arguments
program Project14;
$APPTYPE CONSOLE
$R *.res
uses
System.SysUtils;
type
TAnimal = class
private
FName: string;
end;
TDog = class(TAnimal)
public
constructor Create(Name: string);
end;
TAnimalTrainer = record // class or record
public
procedure Train(constvar aA: TAnimal); overload; // class method or not
procedure Train(const aName: string); overload;
end;
TAnimalTrainer
procedure TAnimalTrainer.Train(const aName: string);
var
Dog: TDog;
begin
Dog := nil;
try
Dog := TDog.Create(aName);
Train(Dog); // error here
finally
Dog.Free;
end;
end;
procedure TAnimalTrainer.Train(var aA: TAnimal);
begin
aA := nil;
end;
TDog
constructor TDog.Create(Name: string);
begin
FName := Name;
end;
begin
try
TODO -oUser -cConsole Main : Insert code here
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Found workarounds:
- Omit the
var
. - Cast the local variable to
TAnimal(Dog)
- stick with
const
.
Question: Is this a bug in the compiler?
delphi delphi-10.2-tokyo
|
show 12 more comments
MCVE:
The following code does not compile with error when switching the parameter type from const
to var
or out
in the overloaded method Train
of the class TAnimalTrainer
but it compiles if non is specified.
[dcc32 Error] Project14.dpr(41): E2250 There is no overloaded version
of 'Train' that can be called with these arguments
program Project14;
$APPTYPE CONSOLE
$R *.res
uses
System.SysUtils;
type
TAnimal = class
private
FName: string;
end;
TDog = class(TAnimal)
public
constructor Create(Name: string);
end;
TAnimalTrainer = record // class or record
public
procedure Train(constvar aA: TAnimal); overload; // class method or not
procedure Train(const aName: string); overload;
end;
TAnimalTrainer
procedure TAnimalTrainer.Train(const aName: string);
var
Dog: TDog;
begin
Dog := nil;
try
Dog := TDog.Create(aName);
Train(Dog); // error here
finally
Dog.Free;
end;
end;
procedure TAnimalTrainer.Train(var aA: TAnimal);
begin
aA := nil;
end;
TDog
constructor TDog.Create(Name: string);
begin
FName := Name;
end;
begin
try
TODO -oUser -cConsole Main : Insert code here
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Found workarounds:
- Omit the
var
. - Cast the local variable to
TAnimal(Dog)
- stick with
const
.
Question: Is this a bug in the compiler?
delphi delphi-10.2-tokyo
1
Overloads are picky sometimes, you can cast it to an animal: Train(TAnimal(Dog));
– Sertac Akyuz
Mar 22 at 22:20
@SertacAkyuz or declare it as TAnimal
– Nasreddine Galfout
Mar 22 at 22:21
4
That actually works. You can declare Dog as a TAnimal, even if it contains a TDog instance. The main question is: do you want to change the instance of the animal, so for instance you put in a dog, train it, and get back a cat? Or nil, as in your code.. "Sorry mam, I tried to train your dog, but it died. Here is nil in return") If you don't want that, removevar
, stick withconst
(or not), and you'll be fine. You can then still change the properties of the animal you train, but not the animal itself...
– GolezTrol
Mar 22 at 23:09
1
That depends on if what you call is virtual and the class overrides it. ... In the end, this is a bug.
– Sertac Akyuz
Mar 22 at 23:24
2
@NasreddineGalfout callingFree
on a base class pointer is fine, sinceTObject.Destroy
is virtual. This is a key requirement of polymorphism to allow derived destructors to be called properly.
– Remy Lebeau
Mar 23 at 1:22
|
show 12 more comments
MCVE:
The following code does not compile with error when switching the parameter type from const
to var
or out
in the overloaded method Train
of the class TAnimalTrainer
but it compiles if non is specified.
[dcc32 Error] Project14.dpr(41): E2250 There is no overloaded version
of 'Train' that can be called with these arguments
program Project14;
$APPTYPE CONSOLE
$R *.res
uses
System.SysUtils;
type
TAnimal = class
private
FName: string;
end;
TDog = class(TAnimal)
public
constructor Create(Name: string);
end;
TAnimalTrainer = record // class or record
public
procedure Train(constvar aA: TAnimal); overload; // class method or not
procedure Train(const aName: string); overload;
end;
TAnimalTrainer
procedure TAnimalTrainer.Train(const aName: string);
var
Dog: TDog;
begin
Dog := nil;
try
Dog := TDog.Create(aName);
Train(Dog); // error here
finally
Dog.Free;
end;
end;
procedure TAnimalTrainer.Train(var aA: TAnimal);
begin
aA := nil;
end;
TDog
constructor TDog.Create(Name: string);
begin
FName := Name;
end;
begin
try
TODO -oUser -cConsole Main : Insert code here
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Found workarounds:
- Omit the
var
. - Cast the local variable to
TAnimal(Dog)
- stick with
const
.
Question: Is this a bug in the compiler?
delphi delphi-10.2-tokyo
MCVE:
The following code does not compile with error when switching the parameter type from const
to var
or out
in the overloaded method Train
of the class TAnimalTrainer
but it compiles if non is specified.
[dcc32 Error] Project14.dpr(41): E2250 There is no overloaded version
of 'Train' that can be called with these arguments
program Project14;
$APPTYPE CONSOLE
$R *.res
uses
System.SysUtils;
type
TAnimal = class
private
FName: string;
end;
TDog = class(TAnimal)
public
constructor Create(Name: string);
end;
TAnimalTrainer = record // class or record
public
procedure Train(constvar aA: TAnimal); overload; // class method or not
procedure Train(const aName: string); overload;
end;
TAnimalTrainer
procedure TAnimalTrainer.Train(const aName: string);
var
Dog: TDog;
begin
Dog := nil;
try
Dog := TDog.Create(aName);
Train(Dog); // error here
finally
Dog.Free;
end;
end;
procedure TAnimalTrainer.Train(var aA: TAnimal);
begin
aA := nil;
end;
TDog
constructor TDog.Create(Name: string);
begin
FName := Name;
end;
begin
try
TODO -oUser -cConsole Main : Insert code here
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Found workarounds:
- Omit the
var
. - Cast the local variable to
TAnimal(Dog)
- stick with
const
.
Question: Is this a bug in the compiler?
delphi delphi-10.2-tokyo
delphi delphi-10.2-tokyo
edited Mar 22 at 22:24
Nasreddine Galfout
asked Mar 22 at 22:13
Nasreddine GalfoutNasreddine Galfout
1,5832728
1,5832728
1
Overloads are picky sometimes, you can cast it to an animal: Train(TAnimal(Dog));
– Sertac Akyuz
Mar 22 at 22:20
@SertacAkyuz or declare it as TAnimal
– Nasreddine Galfout
Mar 22 at 22:21
4
That actually works. You can declare Dog as a TAnimal, even if it contains a TDog instance. The main question is: do you want to change the instance of the animal, so for instance you put in a dog, train it, and get back a cat? Or nil, as in your code.. "Sorry mam, I tried to train your dog, but it died. Here is nil in return") If you don't want that, removevar
, stick withconst
(or not), and you'll be fine. You can then still change the properties of the animal you train, but not the animal itself...
– GolezTrol
Mar 22 at 23:09
1
That depends on if what you call is virtual and the class overrides it. ... In the end, this is a bug.
– Sertac Akyuz
Mar 22 at 23:24
2
@NasreddineGalfout callingFree
on a base class pointer is fine, sinceTObject.Destroy
is virtual. This is a key requirement of polymorphism to allow derived destructors to be called properly.
– Remy Lebeau
Mar 23 at 1:22
|
show 12 more comments
1
Overloads are picky sometimes, you can cast it to an animal: Train(TAnimal(Dog));
– Sertac Akyuz
Mar 22 at 22:20
@SertacAkyuz or declare it as TAnimal
– Nasreddine Galfout
Mar 22 at 22:21
4
That actually works. You can declare Dog as a TAnimal, even if it contains a TDog instance. The main question is: do you want to change the instance of the animal, so for instance you put in a dog, train it, and get back a cat? Or nil, as in your code.. "Sorry mam, I tried to train your dog, but it died. Here is nil in return") If you don't want that, removevar
, stick withconst
(or not), and you'll be fine. You can then still change the properties of the animal you train, but not the animal itself...
– GolezTrol
Mar 22 at 23:09
1
That depends on if what you call is virtual and the class overrides it. ... In the end, this is a bug.
– Sertac Akyuz
Mar 22 at 23:24
2
@NasreddineGalfout callingFree
on a base class pointer is fine, sinceTObject.Destroy
is virtual. This is a key requirement of polymorphism to allow derived destructors to be called properly.
– Remy Lebeau
Mar 23 at 1:22
1
1
Overloads are picky sometimes, you can cast it to an animal: Train(TAnimal(Dog));
– Sertac Akyuz
Mar 22 at 22:20
Overloads are picky sometimes, you can cast it to an animal: Train(TAnimal(Dog));
– Sertac Akyuz
Mar 22 at 22:20
@SertacAkyuz or declare it as TAnimal
– Nasreddine Galfout
Mar 22 at 22:21
@SertacAkyuz or declare it as TAnimal
– Nasreddine Galfout
Mar 22 at 22:21
4
4
That actually works. You can declare Dog as a TAnimal, even if it contains a TDog instance. The main question is: do you want to change the instance of the animal, so for instance you put in a dog, train it, and get back a cat? Or nil, as in your code.. "Sorry mam, I tried to train your dog, but it died. Here is nil in return") If you don't want that, remove
var
, stick with const
(or not), and you'll be fine. You can then still change the properties of the animal you train, but not the animal itself...– GolezTrol
Mar 22 at 23:09
That actually works. You can declare Dog as a TAnimal, even if it contains a TDog instance. The main question is: do you want to change the instance of the animal, so for instance you put in a dog, train it, and get back a cat? Or nil, as in your code.. "Sorry mam, I tried to train your dog, but it died. Here is nil in return") If you don't want that, remove
var
, stick with const
(or not), and you'll be fine. You can then still change the properties of the animal you train, but not the animal itself...– GolezTrol
Mar 22 at 23:09
1
1
That depends on if what you call is virtual and the class overrides it. ... In the end, this is a bug.
– Sertac Akyuz
Mar 22 at 23:24
That depends on if what you call is virtual and the class overrides it. ... In the end, this is a bug.
– Sertac Akyuz
Mar 22 at 23:24
2
2
@NasreddineGalfout calling
Free
on a base class pointer is fine, since TObject.Destroy
is virtual. This is a key requirement of polymorphism to allow derived destructors to be called properly.– Remy Lebeau
Mar 23 at 1:22
@NasreddineGalfout calling
Free
on a base class pointer is fine, since TObject.Destroy
is virtual. This is a key requirement of polymorphism to allow derived destructors to be called properly.– Remy Lebeau
Mar 23 at 1:22
|
show 12 more comments
2 Answers
2
active
oldest
votes
Is this a bug in the compiler?
No it is not.
Although you have discovered this in the context of an overloaded method, the overloading is disguising the real problem. It will be much easier to understand the issue if we remove the overload.
So, to that end, consider this program:
type
TAnimal = class
end;
TDog = class(TAnimal)
end;
procedure GetAnimal(var AAnimal: TAnimal);
begin
AAnimal := TAnimal.Create;
end;
var
Dog: TDog;
begin
GetAnimal(Dog);
end.
This fails to compile in the call to GetAnimal
with this error:
[dcc32 Error]: E2033 Types of actual and formal var parameters must be identical
Why does the compiler reject this? Well, imagine if it accepted this. If it did so then when GetAnimal
returned the Dog
variable would refer to an object that was not a TDog
.
To see this, change the body of the program to look like this:
GetAnimal(TAnimal(Dog));
Writeln(Dog.InheritsFrom(TDog));
When you do so, the program compiles, but the output is
FALSE
In the context of your program, the compiler is faced with some overloads. As we have seen in this example, the compiler cannot accept passing a TDog
variable to a TAnimal
var parameter, so it rejects that overload. It knows that it cannot pass a TDog
variable to a string
parameter, so that is rejected. At which point, there are no overloaded methods left, hence the error message.
this really cleared things in my mind.
– Nasreddine Galfout
Mar 23 at 11:00
one more thing why the compiler accepts it if I remove the 'var', I mean I can still do the above. (in my mind when adding 'var' the compiler will not create a second pointer to the first pointer to Dog, I think I read this in one of your answers)
– Nasreddine Galfout
Mar 23 at 11:07
Because the caller can't see the modification
– David Heffernan
Mar 23 at 11:32
I don't understand why the example you give is relevant, the code in the question tries to pass a dog as an animal. If you test if a dog is an animal, it is. ... You would probably argue then, the behavior when you don't use the var is a compiler defect, you shouldn't be able to pass a dog as an animal...
– Sertac Akyuz
Mar 23 at 11:42
1
@Sertac that fails because an animal can be passed in. The example in the question fails because an animal can be passed out.
– David Heffernan
Mar 23 at 14:16
|
show 7 more comments
Basic issue with non matching var
parameters is the possibility that you end up with wrong type inside the calling variable.
You can trick the compiler to do that by using absolute
keyword - that allows you to declare variables of different type that share same space - and simulate what would happen if compiler would allow you to use such construct.
Consider following example
uses
System.SysUtils;
type
TAnimal = class
public
procedure Run; virtual;
end;
TDog = class(TAnimal)
public
procedure Bark; virtual;
procedure Fetch; virtual;
end;
TCat = class(TAnimal)
public
procedure Meow; virtual;
end;
procedure TAnimal.Run;
begin
Writeln('Run');
end;
procedure TDog.Bark;
begin
Writeln('Bark');
end;
procedure TDog.Fetch;
begin
Writeln('Fetch');
end;
procedure TCat.Meow;
begin
Writeln('Meow');
end;
procedure Move(const aA: TAnimal);
begin
aA.Run;
end;
procedure Train(var aA: TAnimal);
begin
aA := TCat.Create;
end;
var
Dog: TDog;
Cat: TAnimal absolute Dog;
begin
try
// we cannot use Dog here, because compiler would refuse to compile such code
// Cat is TAnimal and compiler allows to pass it
// since Dog and Cat variables share same address space that is
// equivalent of calling Train(Dog);
Train(Cat);
Move(Cat);
Dog.Bark;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
If you run above code you will get following output
Run
Meow
Dog
and Cat
variables share the same address space, so when you call Train(Cat)
as the result you will get TCat
instance that you can use either through Cat
or Dog
variable. Basically, you will end up with TCat
instance inside TDog
variable.
Clearly, when you call Dog.Bark
, you should get Bark
as output not Meow
. Meow
is the first method in TCat
just like the Bark
is the first method in TDog
, and when resolving the Bark
address through TCat
virtual method table it will find Meow
method instead. Since both methods have same signature everything is fine if you consider wrong output as being fine.
Now, if you try calling the Dog.Fetch
, the application will crash with AV. There are no matching methods at corresponding address in TCat
class and you are basically calling some uninitialized place in memory instead of proper method.
That explains why var
or out
parameter types must match the caller variable type.
As to why you can pass TDog
or TCat
as TAnimal
const
or value parameter. Both TDog
and TCat
inherit from TAnimal
and whatewer you can do with TAnimal
instance, both TDog
and TCat
support it. They can override particular behavior, so your cat can run differently than your dog, but whatever you do it is well defined. You cannot end up running some inexistent code.
procedure Move(const aA: TAnimal);
begin
aA.Run;
aA.Fetch; // this will fail to compile - there is no Fetch method in TAnimal class
end;
Of course, this does not prevent you to test for particular class and use type casts to call Fetch
if TAnimal
actually is a TDog
.
procedure Move(const aA: TAnimal);
begin
aA.Run;
if aA is TDog then TDog(aA).Fetch;
end;
However, if you abuse typecasting and typecast without checking whether particular variable is actually a TDog
instance, you will again trip into AV.
procedure Move(const aA: TAnimal);
begin
aA.Run;
TDog(aA).Fetch;
end;
Great answer, I should ask more questions like this, you guys are gold when you start talking about pets and how to train them. big fan of the book by the way.
– Nasreddine Galfout
Mar 23 at 23:05
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55308448%2fwhy-i-cant-pass-child-class-instance-when-switching-parameter-type-from-cons%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
Is this a bug in the compiler?
No it is not.
Although you have discovered this in the context of an overloaded method, the overloading is disguising the real problem. It will be much easier to understand the issue if we remove the overload.
So, to that end, consider this program:
type
TAnimal = class
end;
TDog = class(TAnimal)
end;
procedure GetAnimal(var AAnimal: TAnimal);
begin
AAnimal := TAnimal.Create;
end;
var
Dog: TDog;
begin
GetAnimal(Dog);
end.
This fails to compile in the call to GetAnimal
with this error:
[dcc32 Error]: E2033 Types of actual and formal var parameters must be identical
Why does the compiler reject this? Well, imagine if it accepted this. If it did so then when GetAnimal
returned the Dog
variable would refer to an object that was not a TDog
.
To see this, change the body of the program to look like this:
GetAnimal(TAnimal(Dog));
Writeln(Dog.InheritsFrom(TDog));
When you do so, the program compiles, but the output is
FALSE
In the context of your program, the compiler is faced with some overloads. As we have seen in this example, the compiler cannot accept passing a TDog
variable to a TAnimal
var parameter, so it rejects that overload. It knows that it cannot pass a TDog
variable to a string
parameter, so that is rejected. At which point, there are no overloaded methods left, hence the error message.
this really cleared things in my mind.
– Nasreddine Galfout
Mar 23 at 11:00
one more thing why the compiler accepts it if I remove the 'var', I mean I can still do the above. (in my mind when adding 'var' the compiler will not create a second pointer to the first pointer to Dog, I think I read this in one of your answers)
– Nasreddine Galfout
Mar 23 at 11:07
Because the caller can't see the modification
– David Heffernan
Mar 23 at 11:32
I don't understand why the example you give is relevant, the code in the question tries to pass a dog as an animal. If you test if a dog is an animal, it is. ... You would probably argue then, the behavior when you don't use the var is a compiler defect, you shouldn't be able to pass a dog as an animal...
– Sertac Akyuz
Mar 23 at 11:42
1
@Sertac that fails because an animal can be passed in. The example in the question fails because an animal can be passed out.
– David Heffernan
Mar 23 at 14:16
|
show 7 more comments
Is this a bug in the compiler?
No it is not.
Although you have discovered this in the context of an overloaded method, the overloading is disguising the real problem. It will be much easier to understand the issue if we remove the overload.
So, to that end, consider this program:
type
TAnimal = class
end;
TDog = class(TAnimal)
end;
procedure GetAnimal(var AAnimal: TAnimal);
begin
AAnimal := TAnimal.Create;
end;
var
Dog: TDog;
begin
GetAnimal(Dog);
end.
This fails to compile in the call to GetAnimal
with this error:
[dcc32 Error]: E2033 Types of actual and formal var parameters must be identical
Why does the compiler reject this? Well, imagine if it accepted this. If it did so then when GetAnimal
returned the Dog
variable would refer to an object that was not a TDog
.
To see this, change the body of the program to look like this:
GetAnimal(TAnimal(Dog));
Writeln(Dog.InheritsFrom(TDog));
When you do so, the program compiles, but the output is
FALSE
In the context of your program, the compiler is faced with some overloads. As we have seen in this example, the compiler cannot accept passing a TDog
variable to a TAnimal
var parameter, so it rejects that overload. It knows that it cannot pass a TDog
variable to a string
parameter, so that is rejected. At which point, there are no overloaded methods left, hence the error message.
this really cleared things in my mind.
– Nasreddine Galfout
Mar 23 at 11:00
one more thing why the compiler accepts it if I remove the 'var', I mean I can still do the above. (in my mind when adding 'var' the compiler will not create a second pointer to the first pointer to Dog, I think I read this in one of your answers)
– Nasreddine Galfout
Mar 23 at 11:07
Because the caller can't see the modification
– David Heffernan
Mar 23 at 11:32
I don't understand why the example you give is relevant, the code in the question tries to pass a dog as an animal. If you test if a dog is an animal, it is. ... You would probably argue then, the behavior when you don't use the var is a compiler defect, you shouldn't be able to pass a dog as an animal...
– Sertac Akyuz
Mar 23 at 11:42
1
@Sertac that fails because an animal can be passed in. The example in the question fails because an animal can be passed out.
– David Heffernan
Mar 23 at 14:16
|
show 7 more comments
Is this a bug in the compiler?
No it is not.
Although you have discovered this in the context of an overloaded method, the overloading is disguising the real problem. It will be much easier to understand the issue if we remove the overload.
So, to that end, consider this program:
type
TAnimal = class
end;
TDog = class(TAnimal)
end;
procedure GetAnimal(var AAnimal: TAnimal);
begin
AAnimal := TAnimal.Create;
end;
var
Dog: TDog;
begin
GetAnimal(Dog);
end.
This fails to compile in the call to GetAnimal
with this error:
[dcc32 Error]: E2033 Types of actual and formal var parameters must be identical
Why does the compiler reject this? Well, imagine if it accepted this. If it did so then when GetAnimal
returned the Dog
variable would refer to an object that was not a TDog
.
To see this, change the body of the program to look like this:
GetAnimal(TAnimal(Dog));
Writeln(Dog.InheritsFrom(TDog));
When you do so, the program compiles, but the output is
FALSE
In the context of your program, the compiler is faced with some overloads. As we have seen in this example, the compiler cannot accept passing a TDog
variable to a TAnimal
var parameter, so it rejects that overload. It knows that it cannot pass a TDog
variable to a string
parameter, so that is rejected. At which point, there are no overloaded methods left, hence the error message.
Is this a bug in the compiler?
No it is not.
Although you have discovered this in the context of an overloaded method, the overloading is disguising the real problem. It will be much easier to understand the issue if we remove the overload.
So, to that end, consider this program:
type
TAnimal = class
end;
TDog = class(TAnimal)
end;
procedure GetAnimal(var AAnimal: TAnimal);
begin
AAnimal := TAnimal.Create;
end;
var
Dog: TDog;
begin
GetAnimal(Dog);
end.
This fails to compile in the call to GetAnimal
with this error:
[dcc32 Error]: E2033 Types of actual and formal var parameters must be identical
Why does the compiler reject this? Well, imagine if it accepted this. If it did so then when GetAnimal
returned the Dog
variable would refer to an object that was not a TDog
.
To see this, change the body of the program to look like this:
GetAnimal(TAnimal(Dog));
Writeln(Dog.InheritsFrom(TDog));
When you do so, the program compiles, but the output is
FALSE
In the context of your program, the compiler is faced with some overloads. As we have seen in this example, the compiler cannot accept passing a TDog
variable to a TAnimal
var parameter, so it rejects that overload. It knows that it cannot pass a TDog
variable to a string
parameter, so that is rejected. At which point, there are no overloaded methods left, hence the error message.
answered Mar 23 at 10:54
David HeffernanDavid Heffernan
524k358431234
524k358431234
this really cleared things in my mind.
– Nasreddine Galfout
Mar 23 at 11:00
one more thing why the compiler accepts it if I remove the 'var', I mean I can still do the above. (in my mind when adding 'var' the compiler will not create a second pointer to the first pointer to Dog, I think I read this in one of your answers)
– Nasreddine Galfout
Mar 23 at 11:07
Because the caller can't see the modification
– David Heffernan
Mar 23 at 11:32
I don't understand why the example you give is relevant, the code in the question tries to pass a dog as an animal. If you test if a dog is an animal, it is. ... You would probably argue then, the behavior when you don't use the var is a compiler defect, you shouldn't be able to pass a dog as an animal...
– Sertac Akyuz
Mar 23 at 11:42
1
@Sertac that fails because an animal can be passed in. The example in the question fails because an animal can be passed out.
– David Heffernan
Mar 23 at 14:16
|
show 7 more comments
this really cleared things in my mind.
– Nasreddine Galfout
Mar 23 at 11:00
one more thing why the compiler accepts it if I remove the 'var', I mean I can still do the above. (in my mind when adding 'var' the compiler will not create a second pointer to the first pointer to Dog, I think I read this in one of your answers)
– Nasreddine Galfout
Mar 23 at 11:07
Because the caller can't see the modification
– David Heffernan
Mar 23 at 11:32
I don't understand why the example you give is relevant, the code in the question tries to pass a dog as an animal. If you test if a dog is an animal, it is. ... You would probably argue then, the behavior when you don't use the var is a compiler defect, you shouldn't be able to pass a dog as an animal...
– Sertac Akyuz
Mar 23 at 11:42
1
@Sertac that fails because an animal can be passed in. The example in the question fails because an animal can be passed out.
– David Heffernan
Mar 23 at 14:16
this really cleared things in my mind.
– Nasreddine Galfout
Mar 23 at 11:00
this really cleared things in my mind.
– Nasreddine Galfout
Mar 23 at 11:00
one more thing why the compiler accepts it if I remove the 'var', I mean I can still do the above. (in my mind when adding 'var' the compiler will not create a second pointer to the first pointer to Dog, I think I read this in one of your answers)
– Nasreddine Galfout
Mar 23 at 11:07
one more thing why the compiler accepts it if I remove the 'var', I mean I can still do the above. (in my mind when adding 'var' the compiler will not create a second pointer to the first pointer to Dog, I think I read this in one of your answers)
– Nasreddine Galfout
Mar 23 at 11:07
Because the caller can't see the modification
– David Heffernan
Mar 23 at 11:32
Because the caller can't see the modification
– David Heffernan
Mar 23 at 11:32
I don't understand why the example you give is relevant, the code in the question tries to pass a dog as an animal. If you test if a dog is an animal, it is. ... You would probably argue then, the behavior when you don't use the var is a compiler defect, you shouldn't be able to pass a dog as an animal...
– Sertac Akyuz
Mar 23 at 11:42
I don't understand why the example you give is relevant, the code in the question tries to pass a dog as an animal. If you test if a dog is an animal, it is. ... You would probably argue then, the behavior when you don't use the var is a compiler defect, you shouldn't be able to pass a dog as an animal...
– Sertac Akyuz
Mar 23 at 11:42
1
1
@Sertac that fails because an animal can be passed in. The example in the question fails because an animal can be passed out.
– David Heffernan
Mar 23 at 14:16
@Sertac that fails because an animal can be passed in. The example in the question fails because an animal can be passed out.
– David Heffernan
Mar 23 at 14:16
|
show 7 more comments
Basic issue with non matching var
parameters is the possibility that you end up with wrong type inside the calling variable.
You can trick the compiler to do that by using absolute
keyword - that allows you to declare variables of different type that share same space - and simulate what would happen if compiler would allow you to use such construct.
Consider following example
uses
System.SysUtils;
type
TAnimal = class
public
procedure Run; virtual;
end;
TDog = class(TAnimal)
public
procedure Bark; virtual;
procedure Fetch; virtual;
end;
TCat = class(TAnimal)
public
procedure Meow; virtual;
end;
procedure TAnimal.Run;
begin
Writeln('Run');
end;
procedure TDog.Bark;
begin
Writeln('Bark');
end;
procedure TDog.Fetch;
begin
Writeln('Fetch');
end;
procedure TCat.Meow;
begin
Writeln('Meow');
end;
procedure Move(const aA: TAnimal);
begin
aA.Run;
end;
procedure Train(var aA: TAnimal);
begin
aA := TCat.Create;
end;
var
Dog: TDog;
Cat: TAnimal absolute Dog;
begin
try
// we cannot use Dog here, because compiler would refuse to compile such code
// Cat is TAnimal and compiler allows to pass it
// since Dog and Cat variables share same address space that is
// equivalent of calling Train(Dog);
Train(Cat);
Move(Cat);
Dog.Bark;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
If you run above code you will get following output
Run
Meow
Dog
and Cat
variables share the same address space, so when you call Train(Cat)
as the result you will get TCat
instance that you can use either through Cat
or Dog
variable. Basically, you will end up with TCat
instance inside TDog
variable.
Clearly, when you call Dog.Bark
, you should get Bark
as output not Meow
. Meow
is the first method in TCat
just like the Bark
is the first method in TDog
, and when resolving the Bark
address through TCat
virtual method table it will find Meow
method instead. Since both methods have same signature everything is fine if you consider wrong output as being fine.
Now, if you try calling the Dog.Fetch
, the application will crash with AV. There are no matching methods at corresponding address in TCat
class and you are basically calling some uninitialized place in memory instead of proper method.
That explains why var
or out
parameter types must match the caller variable type.
As to why you can pass TDog
or TCat
as TAnimal
const
or value parameter. Both TDog
and TCat
inherit from TAnimal
and whatewer you can do with TAnimal
instance, both TDog
and TCat
support it. They can override particular behavior, so your cat can run differently than your dog, but whatever you do it is well defined. You cannot end up running some inexistent code.
procedure Move(const aA: TAnimal);
begin
aA.Run;
aA.Fetch; // this will fail to compile - there is no Fetch method in TAnimal class
end;
Of course, this does not prevent you to test for particular class and use type casts to call Fetch
if TAnimal
actually is a TDog
.
procedure Move(const aA: TAnimal);
begin
aA.Run;
if aA is TDog then TDog(aA).Fetch;
end;
However, if you abuse typecasting and typecast without checking whether particular variable is actually a TDog
instance, you will again trip into AV.
procedure Move(const aA: TAnimal);
begin
aA.Run;
TDog(aA).Fetch;
end;
Great answer, I should ask more questions like this, you guys are gold when you start talking about pets and how to train them. big fan of the book by the way.
– Nasreddine Galfout
Mar 23 at 23:05
add a comment |
Basic issue with non matching var
parameters is the possibility that you end up with wrong type inside the calling variable.
You can trick the compiler to do that by using absolute
keyword - that allows you to declare variables of different type that share same space - and simulate what would happen if compiler would allow you to use such construct.
Consider following example
uses
System.SysUtils;
type
TAnimal = class
public
procedure Run; virtual;
end;
TDog = class(TAnimal)
public
procedure Bark; virtual;
procedure Fetch; virtual;
end;
TCat = class(TAnimal)
public
procedure Meow; virtual;
end;
procedure TAnimal.Run;
begin
Writeln('Run');
end;
procedure TDog.Bark;
begin
Writeln('Bark');
end;
procedure TDog.Fetch;
begin
Writeln('Fetch');
end;
procedure TCat.Meow;
begin
Writeln('Meow');
end;
procedure Move(const aA: TAnimal);
begin
aA.Run;
end;
procedure Train(var aA: TAnimal);
begin
aA := TCat.Create;
end;
var
Dog: TDog;
Cat: TAnimal absolute Dog;
begin
try
// we cannot use Dog here, because compiler would refuse to compile such code
// Cat is TAnimal and compiler allows to pass it
// since Dog and Cat variables share same address space that is
// equivalent of calling Train(Dog);
Train(Cat);
Move(Cat);
Dog.Bark;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
If you run above code you will get following output
Run
Meow
Dog
and Cat
variables share the same address space, so when you call Train(Cat)
as the result you will get TCat
instance that you can use either through Cat
or Dog
variable. Basically, you will end up with TCat
instance inside TDog
variable.
Clearly, when you call Dog.Bark
, you should get Bark
as output not Meow
. Meow
is the first method in TCat
just like the Bark
is the first method in TDog
, and when resolving the Bark
address through TCat
virtual method table it will find Meow
method instead. Since both methods have same signature everything is fine if you consider wrong output as being fine.
Now, if you try calling the Dog.Fetch
, the application will crash with AV. There are no matching methods at corresponding address in TCat
class and you are basically calling some uninitialized place in memory instead of proper method.
That explains why var
or out
parameter types must match the caller variable type.
As to why you can pass TDog
or TCat
as TAnimal
const
or value parameter. Both TDog
and TCat
inherit from TAnimal
and whatewer you can do with TAnimal
instance, both TDog
and TCat
support it. They can override particular behavior, so your cat can run differently than your dog, but whatever you do it is well defined. You cannot end up running some inexistent code.
procedure Move(const aA: TAnimal);
begin
aA.Run;
aA.Fetch; // this will fail to compile - there is no Fetch method in TAnimal class
end;
Of course, this does not prevent you to test for particular class and use type casts to call Fetch
if TAnimal
actually is a TDog
.
procedure Move(const aA: TAnimal);
begin
aA.Run;
if aA is TDog then TDog(aA).Fetch;
end;
However, if you abuse typecasting and typecast without checking whether particular variable is actually a TDog
instance, you will again trip into AV.
procedure Move(const aA: TAnimal);
begin
aA.Run;
TDog(aA).Fetch;
end;
Great answer, I should ask more questions like this, you guys are gold when you start talking about pets and how to train them. big fan of the book by the way.
– Nasreddine Galfout
Mar 23 at 23:05
add a comment |
Basic issue with non matching var
parameters is the possibility that you end up with wrong type inside the calling variable.
You can trick the compiler to do that by using absolute
keyword - that allows you to declare variables of different type that share same space - and simulate what would happen if compiler would allow you to use such construct.
Consider following example
uses
System.SysUtils;
type
TAnimal = class
public
procedure Run; virtual;
end;
TDog = class(TAnimal)
public
procedure Bark; virtual;
procedure Fetch; virtual;
end;
TCat = class(TAnimal)
public
procedure Meow; virtual;
end;
procedure TAnimal.Run;
begin
Writeln('Run');
end;
procedure TDog.Bark;
begin
Writeln('Bark');
end;
procedure TDog.Fetch;
begin
Writeln('Fetch');
end;
procedure TCat.Meow;
begin
Writeln('Meow');
end;
procedure Move(const aA: TAnimal);
begin
aA.Run;
end;
procedure Train(var aA: TAnimal);
begin
aA := TCat.Create;
end;
var
Dog: TDog;
Cat: TAnimal absolute Dog;
begin
try
// we cannot use Dog here, because compiler would refuse to compile such code
// Cat is TAnimal and compiler allows to pass it
// since Dog and Cat variables share same address space that is
// equivalent of calling Train(Dog);
Train(Cat);
Move(Cat);
Dog.Bark;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
If you run above code you will get following output
Run
Meow
Dog
and Cat
variables share the same address space, so when you call Train(Cat)
as the result you will get TCat
instance that you can use either through Cat
or Dog
variable. Basically, you will end up with TCat
instance inside TDog
variable.
Clearly, when you call Dog.Bark
, you should get Bark
as output not Meow
. Meow
is the first method in TCat
just like the Bark
is the first method in TDog
, and when resolving the Bark
address through TCat
virtual method table it will find Meow
method instead. Since both methods have same signature everything is fine if you consider wrong output as being fine.
Now, if you try calling the Dog.Fetch
, the application will crash with AV. There are no matching methods at corresponding address in TCat
class and you are basically calling some uninitialized place in memory instead of proper method.
That explains why var
or out
parameter types must match the caller variable type.
As to why you can pass TDog
or TCat
as TAnimal
const
or value parameter. Both TDog
and TCat
inherit from TAnimal
and whatewer you can do with TAnimal
instance, both TDog
and TCat
support it. They can override particular behavior, so your cat can run differently than your dog, but whatever you do it is well defined. You cannot end up running some inexistent code.
procedure Move(const aA: TAnimal);
begin
aA.Run;
aA.Fetch; // this will fail to compile - there is no Fetch method in TAnimal class
end;
Of course, this does not prevent you to test for particular class and use type casts to call Fetch
if TAnimal
actually is a TDog
.
procedure Move(const aA: TAnimal);
begin
aA.Run;
if aA is TDog then TDog(aA).Fetch;
end;
However, if you abuse typecasting and typecast without checking whether particular variable is actually a TDog
instance, you will again trip into AV.
procedure Move(const aA: TAnimal);
begin
aA.Run;
TDog(aA).Fetch;
end;
Basic issue with non matching var
parameters is the possibility that you end up with wrong type inside the calling variable.
You can trick the compiler to do that by using absolute
keyword - that allows you to declare variables of different type that share same space - and simulate what would happen if compiler would allow you to use such construct.
Consider following example
uses
System.SysUtils;
type
TAnimal = class
public
procedure Run; virtual;
end;
TDog = class(TAnimal)
public
procedure Bark; virtual;
procedure Fetch; virtual;
end;
TCat = class(TAnimal)
public
procedure Meow; virtual;
end;
procedure TAnimal.Run;
begin
Writeln('Run');
end;
procedure TDog.Bark;
begin
Writeln('Bark');
end;
procedure TDog.Fetch;
begin
Writeln('Fetch');
end;
procedure TCat.Meow;
begin
Writeln('Meow');
end;
procedure Move(const aA: TAnimal);
begin
aA.Run;
end;
procedure Train(var aA: TAnimal);
begin
aA := TCat.Create;
end;
var
Dog: TDog;
Cat: TAnimal absolute Dog;
begin
try
// we cannot use Dog here, because compiler would refuse to compile such code
// Cat is TAnimal and compiler allows to pass it
// since Dog and Cat variables share same address space that is
// equivalent of calling Train(Dog);
Train(Cat);
Move(Cat);
Dog.Bark;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
If you run above code you will get following output
Run
Meow
Dog
and Cat
variables share the same address space, so when you call Train(Cat)
as the result you will get TCat
instance that you can use either through Cat
or Dog
variable. Basically, you will end up with TCat
instance inside TDog
variable.
Clearly, when you call Dog.Bark
, you should get Bark
as output not Meow
. Meow
is the first method in TCat
just like the Bark
is the first method in TDog
, and when resolving the Bark
address through TCat
virtual method table it will find Meow
method instead. Since both methods have same signature everything is fine if you consider wrong output as being fine.
Now, if you try calling the Dog.Fetch
, the application will crash with AV. There are no matching methods at corresponding address in TCat
class and you are basically calling some uninitialized place in memory instead of proper method.
That explains why var
or out
parameter types must match the caller variable type.
As to why you can pass TDog
or TCat
as TAnimal
const
or value parameter. Both TDog
and TCat
inherit from TAnimal
and whatewer you can do with TAnimal
instance, both TDog
and TCat
support it. They can override particular behavior, so your cat can run differently than your dog, but whatever you do it is well defined. You cannot end up running some inexistent code.
procedure Move(const aA: TAnimal);
begin
aA.Run;
aA.Fetch; // this will fail to compile - there is no Fetch method in TAnimal class
end;
Of course, this does not prevent you to test for particular class and use type casts to call Fetch
if TAnimal
actually is a TDog
.
procedure Move(const aA: TAnimal);
begin
aA.Run;
if aA is TDog then TDog(aA).Fetch;
end;
However, if you abuse typecasting and typecast without checking whether particular variable is actually a TDog
instance, you will again trip into AV.
procedure Move(const aA: TAnimal);
begin
aA.Run;
TDog(aA).Fetch;
end;
edited Mar 24 at 11:34
answered Mar 23 at 22:38
Dalija PrasnikarDalija Prasnikar
19.1k1054109
19.1k1054109
Great answer, I should ask more questions like this, you guys are gold when you start talking about pets and how to train them. big fan of the book by the way.
– Nasreddine Galfout
Mar 23 at 23:05
add a comment |
Great answer, I should ask more questions like this, you guys are gold when you start talking about pets and how to train them. big fan of the book by the way.
– Nasreddine Galfout
Mar 23 at 23:05
Great answer, I should ask more questions like this, you guys are gold when you start talking about pets and how to train them. big fan of the book by the way.
– Nasreddine Galfout
Mar 23 at 23:05
Great answer, I should ask more questions like this, you guys are gold when you start talking about pets and how to train them. big fan of the book by the way.
– Nasreddine Galfout
Mar 23 at 23:05
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55308448%2fwhy-i-cant-pass-child-class-instance-when-switching-parameter-type-from-cons%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
Overloads are picky sometimes, you can cast it to an animal: Train(TAnimal(Dog));
– Sertac Akyuz
Mar 22 at 22:20
@SertacAkyuz or declare it as TAnimal
– Nasreddine Galfout
Mar 22 at 22:21
4
That actually works. You can declare Dog as a TAnimal, even if it contains a TDog instance. The main question is: do you want to change the instance of the animal, so for instance you put in a dog, train it, and get back a cat? Or nil, as in your code.. "Sorry mam, I tried to train your dog, but it died. Here is nil in return") If you don't want that, remove
var
, stick withconst
(or not), and you'll be fine. You can then still change the properties of the animal you train, but not the animal itself...– GolezTrol
Mar 22 at 23:09
1
That depends on if what you call is virtual and the class overrides it. ... In the end, this is a bug.
– Sertac Akyuz
Mar 22 at 23:24
2
@NasreddineGalfout calling
Free
on a base class pointer is fine, sinceTObject.Destroy
is virtual. This is a key requirement of polymorphism to allow derived destructors to be called properly.– Remy Lebeau
Mar 23 at 1:22