HMH metalanguage params

In the file handmade_generated.h we find

member_definition MembersOf_sim_region[] = 
{
   {MetaMemberFlag_IsPointer, MetaType_world, "World", PointerToU32(&((sim_region *)0)->World)},
   {0, MetaType_world_position, "Origin", PointerToU32(&((sim_region *)0)->Origin)},
/// and so on
};

My first question here is why casting 0 like that? Is this simply a shortcut to access the member in one line of code? Is is there something else going on?

(like for instance error messages: https://stackoverflow.com/questions/7002719/casting-0-to-void)

It's getting the number of bytes offset from the start of the struct. So in the user code, you can do reflection on the structs without explicitly writing each variable out. You can pass the position of the member and type - the position being ((u8 *)sim_region_instance) + byte_offset. Where the byte offset is what was generated in the meta system.

Not sure if this this answers your question.


Edited by Oliver Marsh on

It is different way of writing offsetof(sim_region, Origin). Casey does not like to depend on compiler provided headers much, thus the custom offsetof implementation you see. offsetof comes from standard stddef.h header.

Depending on compiler you use, writing 0 cast to pointer like here may or may not generate UB (it doesn't for MSVC).


Edited by Mārtiņš Možeiko on

It's getting the number of bytes offset from the start of the struct. So in the user code, you can do reflection on the structs without explicitly writing each variable out. You can pass the position of the member and type - the position being ((u8 *)sim_region_instance) + byte_offset. Where the byte offset is what was generated in the meta system.

Not sure if this this answers your question.

Yes, I understood the idea but still not clear how that cast actually returns the number of bytes used by the member. I assumed it returned the address of the first byte of the member?


Replying to OliverMarsh (#26028)

I assumed it returned the address of the first byte of the member?

That is correct. But because object is located at address 0, the offset of first byte of the member will be same as offset of member from beginning of struct.


Replying to da447m (#26033)

the offset of first byte of the member will be same as offset of member from beginning of struct

So he also needs to pack all structs to make sure they won't have padding, right? Isn't this a disadvantage?

Or is there a smart way to detect padding?


Replying to mmozeiko (#26035)

There is no need for that. Padding is taken into account when using offsetof.


Replying to da447m (#26040)

There is no need for that. Padding is taken into account when using offsetof.

Does the compiler pad to a power of two always? If yes then easy, if not how to be sure? Asking cuz really ignorant in these very technical things.

And does it always pad at end of structure?


Edited by da447m on
Replying to ratchetfreak (#26042)

Compiler inserts padding so next member is aligned for its required alignment. For example, on x64 int requires alignment of 4.

At end of struct padding will be inserted so in case struct is placed in array the next array element is correctly alignment.

You can play on godbolt and ask msvc & clang to print out struct layout that includes offset of each member so you can see where padding is inserted and struct alignment: https://godbolt.org/z/x9nPxzb3M


Edited by Mārtiņš Možeiko on
Replying to da447m (#26043)

Just curious, is there any difference between struct B and C:

struct A
{
    double a1;
    char a2
};

struct B
{
    A a;
    char b; // Is there any padding between a and b?
};

struct C
{
    double a1;
    char a2
    char b;
};

Can you also explain more about the flag that you used in Godbolt?


Replying to mmozeiko (#26044)

Put it it godbolt and you'll see: https://godbolt.org/z/qW8fM73Gd

struct A size is 16, because double must be 8-byte aligned. So offset of "char b" from beginning of struct B is 16 and because "char a2" is at offset 8, then there are 7 extra bytes of padding.

And you can see from struct C that "a2" is at offset 7 and "b" at offset 8 - no padding between them.

What to explain about flag in godbolt? You put -fdump-record-layouts for clang and it dumps structure layout.


Edited by Mārtiņš Možeiko on
Replying to longtran2904 (#26046)

I asked it because I was wondering what C++ inheritance is like? Does the derived class have a class Derived : Base { Base base; ... } or class Derived : Base { /* base's members */ ... }? If the latter then how does base = derived; work? In the past, I thought that the assignment just does something like base = *(Base*)&derived; or memcpy it.

Also, the only difference between having struct A inside struct B and having the members of struct A inside struct B is the padding at the end of struct A, right (In these 2 cases the struct A is at the beginning of struct B)?

Regarding the flag, I was asking about the msvc /d1reportSingleClassLayoutS and wondering if there is any way to print all layouts out?


Edited by longtran2904 on
Replying to mmozeiko (#26047)

It's the first option. If Base has any padding, the Derived type will include it's padding members too. Same reason as before - because in array of Base types each element must be aligned properly.

Assignment of value types will do what you write - memcpy or equivalent address-of + cast + dereference.

/d1reportSingleClassLayoutNAME reports layout of one type - NAME, if you want all types then you can use /d1reportAllClassLayout argument.


Replying to longtran2904 (#26083)

For some reason, clang said sizeof T is 16, or am I reading it incorrectly https://godbolt.org/z/PPh9MPn95


Replying to mmozeiko (#26084)

You are correct, yes it is 16. Because clang decided to not have padding at end of base class.

What I told before was only true in MSVC case.

In C++ the binary layout is not really as strict as in C, so every compiler technically is free to do whatever they want.

That's why people are often using plain C functions when creating API for libraries (even when they are written in C++) because they can be called by any compiler - but in C++ case you must compile & link with same compiler and sometimes even same compiler version.


Edited by Mārtiņš Možeiko on