Info
-
Did you know that the layout of struct fields will affect its size/alignment?
Example
struct unpacked {
char a; // size: 1b => size: 4b
int b; // size: 4b => size: 4b
char c; // size: 1b => size: 4b
// ---------
// size: 12b
};
struct packed {
char a; // size: 1b => size: 4b
char b; // size: 1b => size: 4b
int c; // size: 4b => size: 8b
// --------
// size: 8b
};
static_assert(12 == sizeof(unpacked));
static_assert(8 == sizeof(packed));
Puzzle
- Can you implement
is_packed_layout
trait which for given type T will return { true: if the sizeof...(T.fields) == sizeof(T) or alignments of T.fields are sorted, false : otherwise }?
template<class T> constexpr auto is_packed_layout_v;
struct unpacked {
char a; // size: 1b => size: 4b
int b; // size: 4b => size: 4b
char c; // size: 1b => size: 4b
// ---------
// size: 12b
};
struct unpacked1 {
int b; // size: 4b => size: 4b
char a; // size: 1b => size: 8b
int c; // size: 4b => size: 12b
// ---------
// size: 12b
};
struct packed {
char a; // size: 1b => size: 4b
char b; // size: 1b => size: 4b
int c; // size: 4b => size: 8b
// --------
// size: 8b
};
struct packed1 {
int a; // size: 4b => size: 4b
// --------
// size: 4b
};
struct packed2 {
int c; // size: 4b => size: 4b
char a; // size: 1b => size: 8b
char b; // size: 1b => size: 8b
// --------
// size: 8b
};
struct packed3 {
int x; // size: 4b => size: 4b
char a; // size: 1b => size: 8b
char b; // size: 1b => size: 8b
char c; // size: 1b => size: 8b
char d; // size: 1b => size: 8b
int y; // size: 4b => size: 12b
// ---------
// size: 12b
};
struct packed4 {
using a3_t = std::array<char, 3>;
short x; // size: 2b => size: 2b
short y; // size: 2b => size: 4b
a3_t z; // size: 3b => size: 8b
// --------
// size: 8b
};
struct packed5 {
short x; // size: 2b => size: 2b
short y; // size: 2b => size: 4b
short z; // size: 2b => size: 6b
// --------
// size: 6b
};
struct empty {
// size: 1b => size: 1b
// --------
// size: 1b
};
static_assert(12 == sizeof(unpacked));
static_assert(not is_packed_layout_v<unpacked>);
static_assert(12 == sizeof(unpacked1));
static_assert(not is_packed_layout_v<unpacked1>);
static_assert(8 == sizeof(packed));
static_assert(is_packed_layout_v<packed>);
static_assert(4 == sizeof(packed1));
static_assert(is_packed_layout_v<packed1>);
static_assert(8 == sizeof(packed2));
static_assert(is_packed_layout_v<packed2>);
static_assert(12 == sizeof(packed3));
static_assert(is_packed_layout_v<packed3>);
static_assert(8 == sizeof(packed4));
static_assert(is_packed_layout_v<packed4>);
static_assert(6 == sizeof(packed5));
static_assert(is_packed_layout_v<packed5>);
static_assert(1 == sizeof(empty));
static_assert(is_packed_layout_v<empty>);
Solutions
#include <algorithm>
#include <array>
#include <type_traits>
template <class T, class... TArgs> decltype(void(T{std::declval<TArgs>()...}), std::true_type{}) test_is_braces_constructible(int);
template <class, class...> std::false_type test_is_braces_constructible(...);
template <class T, class... TArgs> using is_braces_constructible = decltype(test_is_braces_constructible<T, TArgs...>(0));
struct any_type {
template<class T> constexpr operator T();
};
template<class T> constexpr bool size_of_fields_equals_size_of_struct() noexcept {
constexpr T object{};
if constexpr(is_braces_constructible<T, any_type, any_type, any_type, any_type, any_type, any_type>{}) {
auto&& [m0, m1, m2, m3, m4, m5] = object;
return sizeof(T) == sizeof(m0) + sizeof(m1) + sizeof(m2) + sizeof(m3) + sizeof(m4) + sizeof(m5);
} else if constexpr(is_braces_constructible<T, any_type, any_type, any_type, any_type, any_type>{}) {
auto&& [m0, m1, m2, m3, m4] = object;
return sizeof(T) == sizeof(m0) + sizeof(m1) + sizeof(m2) + sizeof(m3) + sizeof(m4);
} else if constexpr(is_braces_constructible<T, any_type, any_type, any_type, any_type>{}) {
auto&& [m0, m1, m2, m3] = object;
return sizeof(T) == sizeof(m0) + sizeof(m1) + sizeof(m2) + sizeof(m3);
} else if constexpr(is_braces_constructible<T, any_type, any_type, any_type>{}) {
auto&& [m0, m1, m2] = object;
return sizeof(T) == sizeof(m0) + sizeof(m1) + sizeof(m2);
} else if constexpr(is_braces_constructible<T, any_type, any_type>{}) {
auto&& [m0, m1] = object;
return sizeof(T) == sizeof(m0) + sizeof(m1);
} else if constexpr(is_braces_constructible<T, any_type>{}) {
auto&& [m0] = object;
return sizeof(T) == sizeof(m0);
} else {
return true;
}
}
template<class T> constexpr bool alignments_sorted() noexcept {
constexpr T object{};
if constexpr(is_braces_constructible<T, any_type, any_type, any_type, any_type, any_type, any_type>{}) {
auto&& [m0, m1, m2, m3, m4, m5] = object;
std::array<int, 6> sizes{sizeof(m0), sizeof(m1), sizeof(m2), sizeof(m3), sizeof(m4), sizeof(m5)};
return std::is_sorted(sizes.cbegin(), sizes.cend(), std::less{}) || std::is_sorted(sizes.cbegin(), sizes.cend(), std::greater{});
} else if constexpr(is_braces_constructible<T, any_type, any_type, any_type, any_type, any_type>{}) {
auto&& [m0, m1, m2, m3, m4] = object;
std::array<int, 5> sizes{sizeof(m0), sizeof(m1), sizeof(m2), sizeof(m3), sizeof(m4)};
return std::is_sorted(sizes.cbegin(), sizes.cend(), std::less{}) || std::is_sorted(sizes.cbegin(), sizes.cend(), std::greater{});
} else if constexpr(is_braces_constructible<T, any_type, any_type, any_type, any_type>{}) {
auto&& [m0, m1, m2, m3] = object;
std::array<int, 4> sizes{sizeof(m0), sizeof(m1), sizeof(m2), sizeof(m3)};
return std::is_sorted(sizes.cbegin(), sizes.cend(), std::less{}) || std::is_sorted(sizes.cbegin(), sizes.cend(), std::greater{});
} else if constexpr(is_braces_constructible<T, any_type, any_type, any_type>{}) {
auto&& [m0, m1, m2] = object;
std::array<int, 3> sizes{sizeof(m0), sizeof(m1), sizeof(m2)};
return std::is_sorted(sizes.cbegin(), sizes.cend(), std::less{}) || std::is_sorted(sizes.cbegin(), sizes.cend(), std::greater{});
} else if constexpr(is_braces_constructible<T, any_type, any_type>{}) {
return true;
} else if constexpr(is_braces_constructible<T, any_type>{}) {
return true;
} else {
return true;
}
}
template<class T> constexpr auto is_packed_layout_v = size_of_fields_equals_size_of_struct<T>() || alignments_sorted<T>();
struct unpacked {
char a; // size: 1b => size: 4b
int b; // size: 4b => size: 4b
char c; // size: 1b => size: 4b
// ---------
// size: 12b
};
struct unpacked1 {
int b; // size: 4b => size: 4b
char a; // size: 1b => size: 8b
int c; // size: 4b => size: 12b
// ---------
// size: 12b
};
struct packed {
char a; // size: 1b => size: 4b
char b; // size: 1b => size: 4b
int c; // size: 4b => size: 8b
// --------
// size: 8b
};
struct packed1 {
int a; // size: 4b => size: 4b
// --------
// size: 4b
};
struct packed2 {
int c; // size: 4b => size: 4b
char a; // size: 1b => size: 8b
char b; // size: 1b => size: 8b
// --------
// size: 8b
};
struct packed3 {
int x; // size: 4b => size: 4b
char a; // size: 1b => size: 8b
char b; // size: 1b => size: 8b
char c; // size: 1b => size: 8b
char d; // size: 1b => size: 8b
int y; // size: 4b => size: 12b
// ---------
// size: 12b
};
struct packed4 {
using a3_t = std::array<char, 3>;
short x; // size: 2b => size: 2b
short y; // size: 2b => size: 4b
a3_t z; // size: 3b => size: 8b
// --------
// size: 8b
};
struct packed5 {
short x; // size: 2b => size: 2b
short y; // size: 2b => size: 4b
short z; // size: 2b => size: 6b
// --------
// size: 6b
};
struct empty {
// size: 1b => size: 1b
// --------
// size: 1b
};
static_assert(12 == sizeof(unpacked));
static_assert(not is_packed_layout_v<unpacked>);
static_assert(12 == sizeof(unpacked1));
static_assert(not is_packed_layout_v<unpacked1>);
static_assert(8 == sizeof(packed));
static_assert(is_packed_layout_v<packed>);
static_assert(4 == sizeof(packed1));
static_assert(is_packed_layout_v<packed1>);
static_assert(8 == sizeof(packed2));
static_assert(is_packed_layout_v<packed2>);
static_assert(12 == sizeof(packed3));
static_assert(is_packed_layout_v<packed3>);
static_assert(8 == sizeof(packed4));
static_assert(is_packed_layout_v<packed4>);
static_assert(6 == sizeof(packed5));
static_assert(is_packed_layout_v<packed5>);
static_assert(1 == sizeof(empty));
static_assert(is_packed_layout_v<empty>);
template<auto Id>
struct alignment {
std::size_t *alignments, *sizeofs{};
template<class T> constexpr operator T() const {
alignments[Id] = alignof(T);
sizeofs[Id] = sizeof(T);
return {};
}
};
template<class T, auto... Ns>
constexpr auto is_packed_layout(std::index_sequence<Ns...>) {
if constexpr(sizeof...(Ns) <= 1) {
return true;
} else if constexpr(requires { T{alignment<Ns>{}...}; }) {
std::size_t alignments[sizeof...(Ns)]{}, sizeofs[sizeof...(Ns)]{};
void(T{alignment<Ns>{alignments, sizeofs}...});
return sizeof(T) == (sizeofs[Ns] + ...) or
std::is_sorted(std::cbegin(alignments), std::cend(alignments), [](const auto lhs, const auto rhs) { return lhs > rhs; }) or
std::is_sorted(std::cbegin(alignments), std::cend(alignments), [](const auto lhs, const auto rhs) { return lhs < rhs; });
} else {
return is_packed_layout<T>(std::make_index_sequence<sizeof...(Ns) - 1>{});
}
}
template<class T> requires std::is_aggregate_v<T>
constexpr std::bool_constant<is_packed_layout<T>(
std::make_index_sequence<sizeof(T)>{})> is_packed_layout_v{};
template <class T>
[[nodiscard]] constexpr auto to_tuple(T&& obj) {
// clang-format off
if constexpr (requires { [&obj] { auto&& [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10] = obj; }; }) {
auto&& [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10] = std::forward<T>(obj);
return std::make_tuple(p1, p2, p3, p4, p5, p6, p7, p8, p8, p10);
} else if constexpr (requires { [&obj] { auto&& [p1, p2, p3, p4, p5, p6, p7, p8, p9] = obj; }; }) {
auto&& [p1, p2, p3, p4, p5, p6, p7, p8, p9] = std::forward<T>(obj);
return std::make_tuple(p1, p2, p3, p4, p5, p6, p7, p8, p9);
} else if constexpr (requires { [&obj] { auto&& [p1, p2, p3, p4, p5, p6, p7, p8] = obj; }; }) {
auto&& [p1, p2, p3, p4, p5, p6, p7, p8] = std::forward<T>(obj);
return std::make_tuple(p1, p2, p3, p4, p5, p6, p7, p8);
} else if constexpr (requires { [&obj] { auto&& [p1, p2, p3, p4, p5, p6, p7] = obj; }; }) {
auto&& [p1, p2, p3, p4, p5, p6, p7] = std::forward<T>(obj);
return std::make_tuple(p1, p2, p3, p4, p5, p6, p7);
} else if constexpr (requires { [&obj] { auto&& [p1, p2, p3, p4, p5, p6] = obj; }; }) {
auto&& [p1, p2, p3, p4, p5, p6] = std::forward<T>(obj);
return std::make_tuple(p1, p2, p3, p4, p5, p6);
} else if constexpr (requires { [&obj] { auto&& [p1, p2, p3, p4, p5] = obj; }; }) {
auto&& [p1, p2, p3, p4, p5] = std::forward<T>(obj);
return std::make_tuple(p1, p2, p3, p4, p5);
} else if constexpr (requires { [&obj] { auto&& [p1, p2, p3, p4] = obj; }; }) {
auto&& [p1, p2, p3, p4] = std::forward<T>(obj);
return std::make_tuple(p1, p2, p3, p4);
} else if constexpr (requires { [&obj] { auto&& [p1, p2, p3] = obj; }; }) {
auto&& [p1, p2, p3] = std::forward<T>(obj);
return std::make_tuple(p1, p2, p3);
} else if constexpr (requires { [&obj] { auto&& [p1, p2] = obj; }; }) {
auto&& [p1, p2] = std::forward<T>(obj);
return std::make_tuple(p1, p2);
} else if constexpr (requires { [&obj] { auto&& [p1] = obj; }; }) {
auto&& [p1] = std::forward<T>(obj);
return std::make_tuple(p1);
} else {
return std::make_tuple();
}
// clang-format on
}
template <class T>
consteval bool is_packed_layout() {
auto t = to_tuple(T{});
if constexpr (std::tuple_size_v<decltype(t)> == 0) {
return true;
} else {
std::size_t sum = 0;
std::size_t current_size = sizeof(std::get<0>(t));
bool ascending = true;
bool descending = true;
std::apply(
[&](auto&&... t) {
((sum += sizeof(t),
ascending = ascending and sizeof(t) >= current_size,
descending = descending and sizeof(t) <= current_size,
current_size = sizeof(t)), ...);
},
t);
return (sum == sizeof(T)) or ascending or descending;
}
}
template<auto Ns>
struct AnyType {
std::size_t &size, &align;
template<class T> constexpr operator T() const {
size = sizeof(T);
align = alignof(T);
return T{};
}
};
template<class T, auto...Ns>
constexpr auto is_packed_layout(std::index_sequence<Ns...>) {
if constexpr(sizeof...(Ns) <=1) {
return true;
} else if constexpr(std::is_constructible_v<T, AnyType<Ns>...>) {
std::array<std::size_t, sizeof...(Ns)> sizeofs, alignofs;
void(T{AnyType<Ns>{sizeofs[Ns], alignofs[Ns]}...});
return sizeof(T) == (sizeofs[Ns] + ...) or
std::is_sorted(std::crbegin(alignofs), std::crend(alignofs)) or
std::is_sorted(std::cbegin(alignofs), std::cend(alignofs));
} else {
return is_packed_layout<T>(std::make_index_sequence<sizeof...(Ns)-1>{});
}
}
template<class T> constexpr auto is_packed_layout_v =
is_packed_layout<T>(std::make_index_sequence<sizeof(T)>{});
namespace detail {
template <class T>
auto is_packed(T v) {
const auto &[... vs] = v;
constexpr auto packed = (sizeof(T) - ... - sizeof(vs)) < alignof(T);
return std::bool_constant<packed>{};
}
} // namespace detail
template <class T>
constexpr auto is_packed_layout_v =
std::is_empty_v<T> or decltype(detail::is_packed(std::declval<T>()))::value;
namespace detail {
auto is_packed(auto v) {
auto&& [... vs] = v;
constexpr auto sizes = std::array<std::size_t, sizeof...(vs)>{sizeof(vs)...};
constexpr auto packed =
std::reduce(sizes.begin(), sizes.end()) == sizeof(v) or
std::is_sorted(sizes.begin(), sizes.end(), std::less{}) or
std::is_sorted(sizes.begin(), sizes.end(), std::greater{});
return std::bool_constant<packed>{};
}
} // namespace detail
template <class T>
inline constexpr bool is_packed_layout_v =
decltype(detail::is_packed(std::declval<T>()))::value
namespace mp = boost::mp;
template <auto Fn>
constexpr auto sort = [](std::ranges::range auto types) {
std::sort(std::begin(types), std::end(types), Fn);
return types;
};
using mp::operator|;
auto by_size = [](auto lhs, auto rhs) { return lhs.size < rhs.size; };
constexpr auto pack = [](auto t) {
return mp::reflection::to_tuple(t) | sort<by_size>;
};
template <class T> constexpr auto is_packed_layout_v =
sizeof(pack(std::declval<T>())) == sizeof(T);
template<int N>
struct Foo{
template<class T> requires (sizeof(T) >=N)
constexpr operator T() const {
return {};
}
auto constexpr next()
{
return Foo<N+1>{};
}
auto constexpr size()
{
return N;
}
};
template<typename T>
constexpr bool reverse_constructable( auto const & ... args )
{
auto constexpr N = sizeof ...(args);
return [&args...]<auto ... Is>(std::index_sequence<Is...>)
{
auto t = std::tie(args...);
return requires{ T{std::get<N-1-Is>(t)...};};
}( std::make_index_sequence<sizeof...(args)>());
}
template<class T> constexpr auto is_packed_layout(auto first, auto ... args)
{
if constexpr ( reverse_constructable<T>(first.next(),args...) )
{
return is_packed_layout<T>( first.next(), args...);
} else if constexpr ( reverse_constructable<T>(Foo<1>{}, first, args...) )
return is_packed_layout<T>(Foo<1>{}, first, args...);
else
{
auto sum = (first.size()+ ... + args.size() );
int c0, c1 = first.size();
auto is_sorted1 = ((c0 = c1, c0 >= (c1 = args.size())) && ...);
c1 = first.size();
auto is_sorted2 = ((c0 = c1, c0 <= (c1 = args.size())) && ...);
return sum == sizeof(T) || is_sorted1 ||is_sorted2;
}
}
template<class T> constexpr auto is_packed_layout_v= sizeof(T) <=1 || is_packed_layout<T>(Foo<1>{});