I have the following class,
struct employee {
std::string name;
short salary;
std::size_t age;
};
Just as an example, in Linux amd64, the size of the struct is 48 bytes, and the size of std::string is 32, that is, not a multiple.
Now, I need, in a cross-platform way, for employee
to have a size that is a multiple of the size of std::string
(first member).
(Cross-platform could mean, for example, both Linux amd64 and Apple ARM.)
That is, sizeof(employee) % sizeof(std::string) == 0
.
I tried controlling the padding using alignas
for the whole class or the members, but the requirement to be a power of 2 is too restrictive, it seems.
Then I tried to add a char
array at the end.
Still, I had two problems, first, what is the exact size of the array in different platforms at compile-time, and second not adding another member that can screw up the nice aggregate initialization of the class.
For the first, I do this:
struct employee_dummy {
std::string name;
short salary;
std::size_t age;
};
struct employee {
std::string name;
short salary;
std::size_t age;
char padding[(sizeof(employee_dummy)/sizeof(std::string)+1)*sizeof(std::string) - sizeof(employee_dummy)];
};
Note the ugly dummy class, and I don't even know if the logic is correct.
For the second problem, I don't have a solution. I could do this, but then I would need to add a constructor, the class would not be an aggregate, etc.
struct employee {
std::string name;
short salary;
std::size_t age;
private:
char padding[(sizeof(employee_dummy)/sizeof(std::string)+1)*sizeof(std::string) - sizeof(employee_dummy)];
};
How can I control the size of the struct with standard or non-standard mechanisms and keep the class as an aggregate?
Here is a link to play with this problem empirically: https://cppinsights.io/s/f2fb5239
NOTE ADDED:
I realized that, if the technique to add padding is correct, the calculation is even more difficult because the dummy class might be already adding padding, so I have to take into account the offset of the last element instead.
In this example I want data
to be a multiple of the first member (std::complex
):
struct dummy {
std::complex<double> a;
double b;
std::int64_t b2;
int c;
};
struct data {
std::complex<double> a;
double b;
std::int64_t b2;
int c;
char padding[ ((offsetof(dummy, c) + sizeof(c)) / sizeof(std::complex<double>) + 1)* sizeof(std::complex<double>) - (offsetof(dummy, c) + sizeof(c)) ];
};
Note the formula is even worse now.
Here is a standard-compliant, no ifs or buts, version.
template <template<std::size_t> class tmpl, std::size_t need_multiple_of>
struct adjust_padding
{
template <std::size_t n>
static constexpr std::size_t padding_size()
{
if constexpr (sizeof(tmpl<n>) % need_multiple_of == 0) return n;
else return padding_size<n+1>();
}
using type = tmpl<padding_size<0>()>;
};
Use it like this:
template <std::size_t K>
struct need_strided
{
double x;
const char pad[K];
};
template <>
struct need_strided<0>
{
double x;
};
using strided = adjust_padding<need_strided, 47>::type;
Now strided
has a size that is a multiple of 47 (and of course is aligned correctly). On my computer it is 376.
You can make employee
a template in this fashion:
template <std::size_t K>
struct employee { ...
or make it a member of a template (instead of double x
):
template <std::size_t K>
struct employee_wrapper {
employee e;
and then use employee_wrapper
as a vector element. But provide a specialization for 0 either way.
You can try using std::array
instead of a C-style array and avoid providing a specialization for 0, but it may or may not get optimized out when the size is 0. [[no_unique_address]]
(C++20) may help here.
Note, something like adjust_padding<need_strided, 117>::type
may overflow the default constexpr depth of your compiler.