Search code examples
c++structalignmentpaddingsizeof

How to control the size of a class to be a multiple of the size of a member?


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.


Solution

  • 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.