1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DOM_CANVAS_TIED_FIELDS_H
#define DOM_CANVAS_TIED_FIELDS_H
#include "TupleUtils.h"
namespace mozilla {
// -
/**
* TiedFields(T&) -> std::tuple<Fields&...>
* TiedFields(const T&) -> std::tuple<const Fields&...>
*
* You can also overload TiedFields without adding T::MutTiedFields:
* template<>
* inline auto TiedFields<gfx::IntSize>(gfx::IntSize& a) {
* return std::tie(a.width, a.height);
* }
*/
template <class T>
constexpr auto TiedFields(T& t) {
const auto fields = t.MutTiedFields();
return fields;
}
template <class T, class... Args, class Tup = std::tuple<Args&...>>
constexpr auto TiedFields(const T& t) {
// Uncast const to get mutable-fields tuple, but reapply const to tuple args.
// We should do better than this when C++ gets a solution other than macros.
const auto mutFields = TiedFields(const_cast<T&>(t));
return ToTupleOfConstRefs(mutFields);
}
/**
* Returns true if all bytes in T are accounted for via size of all tied fields.
* Returns false if there's bytes unaccounted for, which might indicate either
* unaccounted-for padding or missing fields.
* The goal is to check that TiedFields returns every field in T, and this
* returns false if it suspects there are bytes that are not accounted for by
* TiedFields.
*
* `constexpr` effectively cannot do math on pointers, so it's not possible to
* figure out via `constexpr` whether fields are consecutive or dense.
* However, we can at least compare `sizeof(T)` to the sum of `sizeof(Args...)`
* for `TiedFields(T) -> std::tuple<Args...>`.
*
* See TiedFieldsExamples.
*/
template <class T>
constexpr bool AreAllBytesTiedFields() {
using fieldsT = decltype(TiedFields(std::declval<T>()));
const auto fields_size_sum = SizeofTupleArgs<fieldsT>::value;
const auto t_size = sizeof(T);
return fields_size_sum == t_size;
}
// It's also possible to determine AreAllBytesRecursiveTiedFields:
// https://hackmd.io/@jgilbert/B16qa0Fa9
// -
/**
* Padding<T> can be used to pad out a struct so that it's not implicitly
* padded by struct rules.
* You can also just add your padding to TiedFields, but by explicitly typing
* padding like this, serialization can make a choice whether to copy Padding,
* or instead to omit the copy.
*
* Omitting the copy isn't always faster.
* struct Entry {
* uint16_t key;
* Padding<uint16_t> padding;
* uint32_t val;
* auto MutTiedFields() { return std::tie(key, padding, val); }
* };
* If you serialize Padding, the serialized size is 8, and the compiler will
* optimize serialization to a single 8-byte memcpy.
* If your serialization omits Padding, the serialized size of Entry shrinks
* by 25%. If you have a big list of Entrys, maybe this is a big savings!
* However, by optimizing for size here you sacrifice speed, because this splits
* the single memcpy into two: a 2-byte memcpy and a 4-byte memcpy.
*
* Explicitly marking padding gives callers the option of choosing.
*/
template <class T>
struct Padding {
T ignored;
friend constexpr bool operator==(const Padding&, const Padding&) {
return true;
}
friend constexpr bool operator<(const Padding&, const Padding&) {
return false;
}
};
static_assert(sizeof(Padding<bool>) == 1);
static_assert(sizeof(Padding<bool[2]>) == 2);
static_assert(sizeof(Padding<int>) == 4);
// -
namespace TiedFieldsExamples {
struct Cat {
int i;
bool b;
constexpr auto MutTiedFields() { return std::tie(i, b); }
};
static_assert(sizeof(Cat) == 8);
static_assert(!AreAllBytesTiedFields<Cat>());
struct Dog {
bool b;
int i;
constexpr auto MutTiedFields() { return std::tie(i, b); }
};
static_assert(sizeof(Dog) == 8);
static_assert(!AreAllBytesTiedFields<Dog>());
struct Fish {
bool b;
bool padding[3];
int i;
constexpr auto MutTiedFields() { return std::tie(i, b, padding); }
};
static_assert(sizeof(Fish) == 8);
static_assert(AreAllBytesTiedFields<Fish>());
struct Eel { // Like a Fish, but you can skip serializing the padding.
bool b;
Padding<bool> padding[3];
int i;
constexpr auto MutTiedFields() { return std::tie(i, b, padding); }
};
static_assert(sizeof(Eel) == 8);
static_assert(AreAllBytesTiedFields<Eel>());
// -
// #define LETS_USE_BIT_FIELDS
#ifdef LETS_USE_BIT_FIELDS
# undef LETS_USE_BIT_FIELDS
struct Platypus {
short s : 1;
short s2 : 1;
int i;
constexpr auto MutTiedFields() {
return std::tie(s, s2, i); // Error: Can't take reference to bit-field.
}
};
#endif
// -
struct FishTank {
Fish f;
int i2;
constexpr auto MutTiedFields() { return std::tie(f, i2); }
};
static_assert(sizeof(FishTank) == 12);
static_assert(AreAllBytesTiedFields<FishTank>());
struct CatCarrier {
Cat c;
int i2;
constexpr auto MutTiedFields() { return std::tie(c, i2); }
};
static_assert(sizeof(CatCarrier) == 12);
static_assert(AreAllBytesTiedFields<CatCarrier>());
static_assert(
!AreAllBytesTiedFields<decltype(CatCarrier::c)>()); // BUT BEWARE THIS!
// For example, if we had AreAllBytesRecursiveTiedFields:
// static_assert(!AreAllBytesRecursiveTiedFields<CatCarrier>());
} // namespace TiedFieldsExamples
} // namespace mozilla
#endif // DOM_CANVAS_TIED_FIELDS_H
|