Is there a way to perform automatic fields reordering in C-like structs? I mean the using of the language features like( preprocessor for C and C++ and templates/type traits/etc for C++), which make it possible to do the following macro (Boost.Fusion-like style to adapt structures):
REARRANGE(StructureName,
(int8_t)(FieldName1),
(int32_t)(FieldName2),
(int16_t)(FieldName3),
(int32_t)(FieldName4));
// is equivalent to (without loss of generality):
struct StructureName
{
int32_t FieldName2;
int32_t FieldName4;
int16_t FieldName3;
int8_t FieldName1;
};
Of course, approach should take into account alignof
values (together with sizeof
) for fields and, if it possible, #pragma pack
current value.
I am aware of bad portability of the result, but it for local use only.
It is mandatory thing to save the field names together with respective types.
The aim is to reduce total structure size.
I found the C++14 solution:
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/repetition/enum.hpp>
#include <utility>
#include <cstddef>
namespace details
{
template< std::size_t /*index*/, typename /*tag*/ >
struct member;
struct pair
{
std::size_t k, v;
constexpr bool operator < (pair const & r) const { return r.k < k; }
};
constexpr void swap(pair & l, pair & r) { pair m = r; r = l; l = m; }
template< int N >
constexpr
void qsort(pair (&a)[N], int const l, int const r)
{
int i = l, j = r;
pair pivot = a[l + (r - l) / 2];
while (!(j < i)) {
while (a[i] < pivot) ++i;
while (pivot < a[j]) --j;
if (!(j < i)) {
swap(a[i], a[j]);
++i;
--j;
}
}
if (l < j) qsort(a, l, j);
if (i < r) qsort(a, i, r);
}
template< int N >
struct map
{
pair a[N];
};
template< int N, std::size_t ...indices >
constexpr
map< N > make_map(pair (&a)[N], std::index_sequence< indices... >)
{
return {{a[indices]...}};
}
template< int N >
constexpr
map< N > qsort(pair (&&a)[N])
{
if (1 < N) {
qsort< N >(a, 0, N - 1);
}
return make_map< N >(a, std::make_index_sequence< N >{});
}
}
#define GEN0(z, tag, index, type_name) template<> struct member< index, tag > \
{ BOOST_PP_SEQ_HEAD(type_name) BOOST_PP_SEQ_HEAD(BOOST_PP_SEQ_TAIL(type_name)); };
#define GEN1(z, ignored, index, type_name) {sizeof(BOOST_PP_SEQ_HEAD(type_name)), index},
#define GEN2(z, index, tags) details::member< BOOST_PP_SEQ_HEAD(tags)::map.a[index].v, BOOST_PP_SEQ_HEAD(BOOST_PP_SEQ_TAIL(tags)) >
#define GEN(ns, tag, members) \
namespace ns { struct tag; } \
namespace details { BOOST_PP_SEQ_FOR_EACH_I(GEN0, ns::tag, members) } \
namespace details::tags::ns { struct tag { static constexpr auto map = qsort({BOOST_PP_SEQ_FOR_EACH_I(GEN1, %%, members)}); }; } \
namespace ns { struct tag : BOOST_PP_ENUM(BOOST_PP_SEQ_SIZE(members), GEN2, (details::tags::ns::tag)(tag)) {}; }
struct T { char c[3]; };
GEN(user::u2, S, ((char)(c))((int)(i))((T)(t)))
#include <cassert>
int main()
{
using namespace details;
void(member< 0, user::u2::S >{}.c);
void(member< 1, user::u2::S >{}.i);
static_assert(tags::user::u2::S::map.a[0].k == 4);
static_assert(tags::user::u2::S::map.a[1].k == 3);
static_assert(tags::user::u2::S::map.a[2].k == 1);
user::u2::S s{4, {'a', 'b', 'c'}, 'd'};
assert((void *)&s.i == (void *)&s);
assert((void *)&s.t < (void *)&s.c);
static_assert(sizeof(s) == 8);
}
- For MS compiler use #pragma pack, (
#pragma pack(1)
eliminates all gaps). And check the link for details, as this directive is not guaranteed to work always.
- For GCC there is
__attribute__ ((packed))
As your only goal is the minimal possible size of the data in memory, this is exactly what you need.