3
\$\begingroup\$

Using constexpr and preprocessor magic it is possible to cook a struct which have a minimal size between all possible sizes, keeping all the data members properly aligned.

#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 > \
{ using type = BOOST_PP_SEQ_HEAD(type_name); type BOOST_PP_SEQ_HEAD(BOOST_PP_SEQ_TAIL(type_name)); };

#define GEN2(z, ignored, index, type_name) {sizeof(BOOST_PP_SEQ_HEAD(type_name)), index},

#define GEN3(z, index, tag) details::member< tag::a.a[index].v, tag >

#define GEN(ns, tag, members) namespace details { \
namespace tags::ns { struct tag; } \
BOOST_PP_SEQ_FOR_EACH_I(GEN0, tags::ns::tag, members) \
namespace tags::ns { \
struct tag \
{ \
    static constexpr auto a = qsort({BOOST_PP_SEQ_FOR_EACH_I(GEN2, %%, members)}); \
}; \
} \
} \
namespace ns { \
using tag = struct : BOOST_PP_ENUM(BOOST_PP_SEQ_SIZE(members), GEN3, details::tags::ns::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, tags::user::u2::S >{}.c);
    void(member< 1, tags::user::u2::S >{}.i);
    static_assert(tags::user::u2::S::a.a[0].k == 4);
    static_assert(tags::user::u2::S::a.a[1].k == 3);
    static_assert(tags::user::u2::S::a.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);
}

Live example (works for both clang and gcc).

Here is mapping of sizeof to indidces sorted by former, then type alias in desired namespace is created.

As you can see the code is dirty. Don't know what is optimal way to use BOOST_PP_* macro family and how to elaborate a better design (optional namespace, templated, allow to define struct not only in global namespace, etc (obvious things)).

\$\endgroup\$
1
  • \$\begingroup\$ What's your specific concern you want to see improvement for? Could you please elaborate that in your question? \$\endgroup\$ Commented Jan 12, 2017 at 20:12

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.