5 ms·
> Related to this, the IsA trait allows typesafe compile-time checked casts. Unlike in C code, casting to a superclass can be written in such a way that the com
by o11c 2y ago
> Related to this, the IsA trait allows typesafe compile-time checked casts.
Unlike in C code, casting to a superclass can be written in such a way
that the compiler will complain if the destination type is not a
superclass, and with zero runtime cost.
This is unfair to C. With a little work when defining all classes (or non-leaf classes if you're willing for a hairier implementation), you can do this there too.
There are probably other ways, but the way that seems most "obvious" to me is to define the base via union of all bases in the chain, rather than only the immediate base class. So:
/*
class Foo {int f;};
class Bar extends Foo {int b;};
class Qux extends Bar {int q;};
class Leaf extends Qux {int l;};
*/
struct Foo { int f; };
struct Bar { union { struct Foo base, base_Foo}; int b; };
struct Qux { union { struct Bar base, base_Bar; struct Foo base_Foo; }; int q; };
struct Leaf { struct Qux base; int l; }
Then your "compile-time-safe-cast to base class" macro just checks 3 things in order:
1. check if we're already the right class.
2. check if `base` is the right class.
3. unconditionally try to use the appropriately-named `base_Foo` member as calculated from the type.
(runtime-safe-checked downcasts just have to check that the reverse is possible)
- bonzini 2y ago"Your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should." :D Though I admit that Rust also has to include the full list of base classes (https://github.com/rust-lang/rfcs/pull/1268 https://github.com/rust-lang/rfcs/pull/1268 would fix it).
- o11c 2y agoThe main reason the C version must hard-code is because C macros can't be recursive. What's Rust's excuse? (that said, the C approach is also nice for manually accessing fields of distant base classes)
- bonzini 2y agoIn Rust's case it is that when you say "you have IsA<Device> for all types that implement IsA<PCIDevice>" you are actually saying "you have IsA<Device> for all types, but only if they implement IsA<PCIDevice>". When you later say "you have IsA<Device> for all types, but only if they implement IsA<I2CDevice>" the two conditions conflict. It might be possible to avoid this issue with a different implementation but this is the simplest one that works and QEMU's hierarchy is generally shallow. If a better implementation came along, it would be a matter of search and repeat.
- jefbyokyie 2y ago> define the base via union of all bases in the chain [...] just checks 3 things in order Can you please show a concrete example? Thanks.
- david-gpu 2y agoAs a recovering language lawyer, I need to point out that what you are describing is not portable behavior because it is writing to one member of a union and reading from a different member, if I understood you correctly. Will it work on most tool chains? Yes. But when it doesn't, it is going to be fun.
- bonzini 2y agoNo, you only need to assert that the field with that name exists.
- flohofwoe 2y agoAFAIK this is legal in C but not in C++, e.g. type punning via union is explicitly allowed in C (one of many subtle differences between actual C and the C subset of C++).
- david-gpu 2y agoLooking at the ISO 9899:2018 spec, a few sections appear to be relevant. This one I wasn't familiar with, and I believe it absolves the comment I initially replied to in this thread. > 6.5.2.3.6 states: > One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible Outside of that special case, the general semantics of unions are specified as follows. I think that means you are right? > 6.2.6.1.7 states > When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values
- flohofwoe 2y agoThere is also a footnote: 99) If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called "type punning"). This might be a trap representation. ...it all makes sense until that last sentence, maybe that's for really weird bit casts like trying to reinterpret an integer as float. FWIW the only place I used union type punning was in an Z80 emulator for the 16-bit register pairs, but that's no longer necessary, since modern compilers will optimize two consequitive byte reads into a single word read: https://www.godbolt.org/z/or7Woc4TP https://www.godbolt.org/z/or7Woc4TP