Search code examples
c++sizeoftype-traitsmemory-alignmentstdtuple

Is it possible to determine the size of a type from the types of its data members


Problem:

Consider the following type:

struct S {
    std::uint8_t a;
    std::uint32_t b;
};

Is it possible to determine sizeof(S) from std::uint8_t and std::uint32_t solely?

Context:

I'm developing a code where S is actually a template type more verbose than this illustration-purpose one, so I thought in defining a traits class for generically determining the size of a type from the types of its data members. So far, I had two ideas:

  1. Using alignment of data members
template <typename... Ts>
inline constexpr std::size_t aligned_sizeof_all_v = sizeof...(Ts) * std::max(alignof(Ts)...);
  1. Using std::tuple
template <typename... Ts>
inline constexpr std::size_t aligned_sizeof_all_v = sizeof(std::tuple<Ts...>);

Using both approaches seem to succeed, although I'm reluctant about the second one since the implementation of std::tuple provides no guarantees with respect to its size:

static_assert(aligned_sizeof_all_v<std::uint8_t, std::uint32_t> == sizeof(S));

Is this a correct way of determining a type's size?


Solution

  • Yes. If the types and order of the object's members are known, and if all members have the same access specifier (e.g. public), and if the object lacks a vtable, you can rebuild the object's structure within a helper class and use the size of that:

    template<typename... Ts>
    struct struct_traits {
        static constexpr std::size_t size() {
            return sizeof(struct_builder<Ts...>);
        }
    
    private:
        template<typename First, typename... Rest>
        struct struct_builder : struct_builder<Rest...> {
            First v;
        };
        template<typename First>
        struct struct_builder<First> {
            First v;
        };
    
        // Below: addition to work with structures declared with alignas().
    public:
        template<std::size_t alignas_>
        static constexpr std::size_t size() {
            return sizeof(aligned_struct_builder<alignas_, Ts...>);
        }
    
    private:
        template<std::size_t alignas_, typename First, typename... Rest>
        struct alignas(alignas_) aligned_struct_builder : aligned_struct_builder<alignas_, Rest...> {
            First v;
        };
        template<std::size_t alignas_, typename First>
        struct alignas(alignas_) aligned_struct_builder<alignas_, First> {
            First v;
        };
    };
    

    Example of use (try it on Godbolt):

    struct S
    { 
        uint8_t a, b;
        uint32_t c;
    };
    
    static_assert(struct_traits<uint8_t, uint8_t, uint32_t>::size() == sizeof(S)); // True! 8 == 8
    static_assert(struct_traits<uint8_t, uint32_t, uint8_t>::size() == sizeof(S)); // Bad: 12 != 8
    

    Use against alignas-ed structure (on Godbolt):

    struct alignas(8) S
    { 
        uint8_t a;
        uint32_t c;
        uint8_t b;
    };
    
    static_assert(struct_traits<uint8_t, uint32_t, uint8_t>::size<8>(), sizeof(S)); // True: 16 == 16
    

    Inspiration from this SO post.