dimanche 30 juin 2019

Array of aligned structure that induces a regular array of individual members

If I have an structure (e.g. employee below), an array of such structure induces an strided array of members of the structure only if the size of the structure is a (least) common multiple (LCM) of the size all members.

Otherwise there will be pointers to specific members in the array that will not have integral pointer distances (measured in the size of the member class).

So, for example, given this structure:

struct employee{
    std::string name;
    short salary;
    std::size_t age;
};

A std::vector<employee> v (or an array employee[N] for that matter) induces a strided array of salary members (with stride sizeof(employee)/sizeof(short)) and also a strided arrays of age.

That is, an array of salaries is random accessed by &(v.data()->salary) + sizeof(employee)/sizeof(short)* n.

However it doesn't induce a stride array of names, because sizeof(employee) (=48) is not a multiple of sizeof(std::string) (32) (in my system).

Of course, I could define the struct in this other way to allow this:

struct alignas(32) employee{ // or alignas(sizeof(std::string)) std::string name; short salary; std::size_t age; employee(short salary, std::size_t age) : salary{salary}, age{age}{} };

I am wondering if finding this proper alignas argument is the only way to achieve this. Also, if there is an automatic way to obtain that number with out manually having to find the common multiple.

I seems that the most general way to do this, without reflection, is to do something like:

struct alignas(LCM(sizeof(std::string), sizeof(short), sizeof(std::size_t) ) employee{ std::string name; short salary; std::size_t age; employee(short salary, std::size_t age) : salary{salary}, age{age}{} };

That is, I have to enumerate all the members in advance. I could use constexpr std::lcm by chaining it several times).

Is this the proper way to do it?

Also, one can always find pathological cases in which this doesn't even work because there are extra restrictions that the alignment needs to be a power of 2 (in some systems). In which case the common multiple needs to be also a power of 2 and can that be a huge number:

using password_type = std::array<char, 103>; // suppose I cannot change the 103

struct alignas(std::lcm(std::lcm(std::lcm(sizeof(std::string), sizeof(short)), sizeof(std::size_t)), sizeof(password_type))) employee{ // bad alignment
    password_type password;
    std::string name;
    short salary;
    std::size_t age;
    employee(short salary, std::size_t age) : salary{salary}, age{age}{}
};

...error: requested alignment ‘3296’ is not a positive power of 2

For the alignment LCM to work I have to manually change the alignment of the specific member or add constexpr ceiling to the nearest power of 2.

struct alignas(std::lcm(std::lcm(std::lcm(sizeof(std::string), sizeof(short)), sizeof(std::size_t)), 128)) employee{ // bad alignment
    alignas(128) password_type password;
    std::string name;
    short salary;
    std::size_t age;
    employee(short salary, std::size_t age) : salary{salary}, age{age}{}
};

However that still doesn't solve the problem because alignas is not part of the password_type, it seems that the only solution then is to have a version of std::array that also takes an internal alignment argument! std::aligned_array<char, 103, 128>.

Ok, I still could do this, but at the cost of modifying other classes that where not coupled to employee initially.

struct alignas(128) password_type : std::array<char, 103>{};

and it might end up working. But it is a lot of manual work, and it could change when I change the system, or add new members, etc.

Is there a more automatic way to do this? or some conventions to follow to make this problem less painful

Aucun commentaire:

Enregistrer un commentaire