I'm not sure if this is a bug, an unimplemented feature, or an intentional restriction, so I thought I'd ask here first. Please read the full post before answering, the real question is closer to the bottom. Sorry the post turned out to be that long
Example problem: I have a class name which I know is of type Actor, but I don't know anything else about it. I need to check if this class is an Inventory, and if it is, then print its default MaxAmount.
Seems easy, right? Let's use GetDefaultByType for this:
Code: Select all
class Test
{
static void PrintMaxAmountIfInventory(name className)
{
let defaultInstance = GetDefaultByType((class<Actor>)(className));
let inventory = Inventory(defaultInstance);
if (inventory)
{
Console.Printf("Max amount of %s: %d", className, inventory.MaxAmount);
}
else
{
Console.Printf("%s is not of type Inventory.", className);
}
}
}
Now let's run it and...
Code: Select all
Script error, "zscript.txt:ZSCRIPT" line 6:
Cannot cast a readonly pointer
Oh noes! What has gone wrong?
Actually, this is completely understandable, so this is NOT the question - please read on. This error has appeared because the value GetDefaultByType returns is not an
Actor but a
readonly<Actor> - something akin to a
const AActor* in C++ code. This is because we are not supposed to modify the instance GetDefaultByType has returned. We can only read its fields, and we can call its functions only if they are marked
const. We can't write a new value to any field or call any non-
const function. While I think it would be a useful feature to be able to change actors' defaults sometimes, that is a whole another story and thus also not the point of this thread.
Okay, so we can't cast the result to Inventory because it would violate const-correctness since Inventory is not readonly, and if we were allowed to do such a cast, then we could modify the instance. All right, let's try to cast the result to
readonly<Inventory> then!
Code: Select all
... class and function declarations omitted
let defaultInstance = GetDefaultByType((class<Actor>)(className));
let inventory = (readonly<Inventory>)(defaultInstance);
if (inventory)
{
... rest omitted
Aaand...
Code: Select all
Script error, "zscript.txt:ZSCRIPT" line 6:
Unexpected 'readonly'
Expecting '-' or '+' or '++' or '--' or '(' or 'class' or identifier or string constant or 'super' or '~' or '!' or 'sizeof' or 'alignof' or integer constant or unsigned constant or float constant or name constant or 'false' or 'true' or 'null'
Well, now THIS is weird. Why not allow casting a readonly to another readonly? Const-correctness is almost certainly not violated in this case, as the resulting value would have the same restrictions on it. Okay, maybe we could at least check if the result is an Inventory with the
is operator?
(NB: It seems that this operator checks for exact type without accounting for inheritance, so it wouldn't suit our needs in this problem but I still want to see if it works)
Code: Select all
... class and function declarations omitted
let defaultInstance = GetDefaultByType((class<Actor>)(className));
if (defaultInstance is "Inventory")
{
... rest omitted
Let's see...
Code: Select all
Script error, "zscript.txt:ZSCRIPT" line 7:
Cannot convert Pointer<Class<Actor>readonly > to Pointer<Class<Object>>
Nope. We can't. Even though we don't even get a new value from this operator, so there isn't even a theoretical possibility to violate const-correctness in this case. The error message also doesn't seem to make much sense. "Pointer<Class<Object>>"? Why is that there?
Okay, well, I thought, there has to be some way to achieve what I want! And indeed there is. It turns out that I need to cast not the
instance that is returned by GetDefaultByType but the
class name. Then check if the resulting class is not null. The correct code looks like this:
Code: Select all
class Test
{
static void PrintMaxAmountIfInventory(name className)
{
let inventoryClass = (class<Inventory>)(className);
if (inventoryClass)
{
let inventory = GetDefaultByType(inventoryClass);
Console.Printf("Max amount of %s: %d", className, inventory.MaxAmount);
}
else
{
Console.Printf("Class %s is not of type Inventory.", className);
}
}
}
Yes, this method does sound logical. However, I would really like to know why it is not possible to cast a
readonly<Actor> to a
readonly<Inventory> or use the
is operator on it. It doesn't look like it would make the world explode if it were allowed. Is it simply something that was never implemented because it was never considered, or is there a logical reason behind it?
I'm not sure if this is a bug, an unimplemented feature, or an intentional restriction, so I thought I'd ask here first. Please read the full post before answering, the real question is closer to the bottom. Sorry the post turned out to be that long :(
Example problem: I have a class name which I know is of type Actor, but I don't know anything else about it. I need to check if this class is an Inventory, and if it is, then print its default MaxAmount.
Seems easy, right? Let's use GetDefaultByType for this:
[code]class Test
{
static void PrintMaxAmountIfInventory(name className)
{
let defaultInstance = GetDefaultByType((class<Actor>)(className));
let inventory = Inventory(defaultInstance);
if (inventory)
{
Console.Printf("Max amount of %s: %d", className, inventory.MaxAmount);
}
else
{
Console.Printf("%s is not of type Inventory.", className);
}
}
}[/code]
Now let's run it and...
[code]Script error, "zscript.txt:ZSCRIPT" line 6:
Cannot cast a readonly pointer[/code]
Oh noes! What has gone wrong?
Actually, this is completely understandable, so this is NOT the question - please read on. This error has appeared because the value GetDefaultByType returns is not an [i]Actor[/i] but a [i]readonly<Actor>[/i] - something akin to a [i]const AActor*[/i] in C++ code. This is because we are not supposed to modify the instance GetDefaultByType has returned. We can only read its fields, and we can call its functions only if they are marked [i]const[/i]. We can't write a new value to any field or call any non-[i]const[/i] function. While I think it would be a useful feature to be able to change actors' defaults sometimes, that is a whole another story and thus also not the point of this thread.
Okay, so we can't cast the result to Inventory because it would violate const-correctness since Inventory is not readonly, and if we were allowed to do such a cast, then we could modify the instance. All right, let's try to cast the result to [i]readonly<Inventory>[/i] then!
[code]... class and function declarations omitted
let defaultInstance = GetDefaultByType((class<Actor>)(className));
let inventory = (readonly<Inventory>)(defaultInstance);
if (inventory)
{
... rest omitted[/code]
Aaand...
[code]Script error, "zscript.txt:ZSCRIPT" line 6:
Unexpected 'readonly'
Expecting '-' or '+' or '++' or '--' or '(' or 'class' or identifier or string constant or 'super' or '~' or '!' or 'sizeof' or 'alignof' or integer constant or unsigned constant or float constant or name constant or 'false' or 'true' or 'null'[/code]
Well, now THIS is weird. Why not allow casting a readonly to another readonly? Const-correctness is almost certainly not violated in this case, as the resulting value would have the same restrictions on it. Okay, maybe we could at least check if the result is an Inventory with the [i]is[/i] operator? [s]([b]NB:[/b] It seems that this operator checks for exact type without accounting for inheritance, so it wouldn't suit our needs in this problem but I still want to see if it works)[/s]
[code]... class and function declarations omitted
let defaultInstance = GetDefaultByType((class<Actor>)(className));
if (defaultInstance is "Inventory")
{
... rest omitted[/code]
Let's see...
[code]Script error, "zscript.txt:ZSCRIPT" line 7:
Cannot convert Pointer<Class<Actor>readonly > to Pointer<Class<Object>>[/code]
Nope. We can't. Even though we don't even get a new value from this operator, so there isn't even a theoretical possibility to violate const-correctness in this case. The error message also doesn't seem to make much sense. "Pointer<Class<Object>>"? Why is that there?
Okay, well, I thought, there has to be some way to achieve what I want! And indeed there is. It turns out that I need to cast not the [u]instance[/u] that is returned by GetDefaultByType but the [u]class name[/u]. Then check if the resulting class is not null. The correct code looks like this:
[code]class Test
{
static void PrintMaxAmountIfInventory(name className)
{
let inventoryClass = (class<Inventory>)(className);
if (inventoryClass)
{
let inventory = GetDefaultByType(inventoryClass);
Console.Printf("Max amount of %s: %d", className, inventory.MaxAmount);
}
else
{
Console.Printf("Class %s is not of type Inventory.", className);
}
}
}[/code]
Yes, this method does sound logical. However, I would really like to know why it is not possible to cast a [i]readonly<Actor>[/i] to a [i]readonly<Inventory>[/i] or use the [i]is[/i] operator on it. It doesn't look like it would make the world explode if it were allowed. Is it simply something that was never implemented because it was never considered, or is there a logical reason behind it?