Source code

Revision control

Copy as Markdown

Other Tools

/* 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 mozilla_TiedFields_h
#define mozilla_TiedFields_h
#include <array>
#include <cstddef>
#include <tuple>
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) {
return t.MutTiedFields();
}
template <class T>
constexpr auto TiedFields(const T& t) {
// Uncast const to get mutable-fields tuple, but reapply const to tuple args.
// We can do better than this when we can use C++23 deducing this, though it
// will require implementors to change the method returning fields.
const auto mutFields = TiedFields(const_cast<T&>(t));
return std::apply([](const auto&... f) { return std::tie(f...); }, mutFields);
}
template <class>
struct SizeofTupleArgs;
// c++17 fold expressions make this easy, once we pull out the Args parameter
// pack by constraining the default template:
template <class... Args>
struct SizeofTupleArgs<std::tuple<Args...>>
: std::integral_constant<size_t, (... + sizeof(Args))> {};
/**
* 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:
template <class StructT, size_t FieldId, size_t PrevFieldBeginOffset,
class PrevFieldT, size_t PrevFieldEndOffset, class FieldT,
size_t FieldAlignment = alignof(FieldT)>
struct FieldDebugInfoT {
static constexpr bool IsTightlyPacked() {
return PrevFieldEndOffset % FieldAlignment == 0;
}
};
template <class StructT, class TupleOfFields, size_t FieldId>
struct TightlyPackedFieldEndOffsetT {
template <size_t I>
using FieldTAt = std::remove_reference_t<
typename std::tuple_element<I, TupleOfFields>::type>;
static constexpr size_t Fn() {
constexpr auto num_fields = std::tuple_size_v<TupleOfFields>;
static_assert(FieldId < num_fields);
using PrevFieldT = FieldTAt<FieldId - 1>;
using FieldT = FieldTAt<FieldId>;
constexpr auto prev_field_end_offset =
TightlyPackedFieldEndOffsetT<StructT, TupleOfFields, FieldId - 1>::Fn();
constexpr auto prev_field_begin_offset =
prev_field_end_offset - sizeof(PrevFieldT);
using FieldDebugInfoT =
FieldDebugInfoT<StructT, FieldId, prev_field_begin_offset, PrevFieldT,
prev_field_end_offset, FieldT>;
static_assert(FieldDebugInfoT::IsTightlyPacked(),
"This field was not tightly packed. Is there padding between "
"it and its predecessor?");
return prev_field_end_offset + sizeof(FieldT);
}
};
template <class StructT, class TupleOfFields>
struct TightlyPackedFieldEndOffsetT<StructT, TupleOfFields, 0> {
static constexpr size_t Fn() {
using FieldT = typename std::tuple_element<0, TupleOfFields>::type;
return sizeof(FieldT);
}
};
template <class StructT, class TupleOfFields>
struct TightlyPackedFieldEndOffsetT<StructT, TupleOfFields, size_t(-1)> {
static constexpr size_t Fn() {
// -1 means tuple_size_v<TupleOfFields> -> 0.
static_assert(sizeof(StructT) == 0);
return 0;
}
};
template <class StructT>
constexpr bool AssertTiedFieldsAreExhaustive() {
static_assert(AreAllBytesTiedFields<StructT>());
using TupleOfFields = decltype(TiedFields(std::declval<StructT&>()));
constexpr auto num_fields = std::tuple_size_v<TupleOfFields>;
constexpr auto end_offset_of_last_field =
TightlyPackedFieldEndOffsetT<StructT, TupleOfFields,
num_fields - 1>::Fn();
static_assert(
end_offset_of_last_field == sizeof(StructT),
"Incorrect field list in MutTiedFields()? (or not tightly-packed?)");
return true; // Support `static_assert(AssertTiedFieldsAreExhaustive())`.
}
/**
* PaddingField<T,N=1> can be used to pad out a struct so that it's not
* implicitly padded by struct rules, but also can't be accidentally initialized
* via Aggregate Initialization. (TiedFields serialization checks rely on object
* fields leaving no implicit padding bytes, but explicit padding fields are
* fine) While you can use e.g. `uint8_t _padding[3];`, consider instead
* `PaddingField<uint8_t,3> _padding;` for clarity and to move the `3` nearer
* to the `uint8_t`.
*/
template <class T, size_t N = 1>
struct PaddingField {
static_assert(!std::is_array_v<T>, "Use PaddingField<T,N> not <T[N]>.");
std::array<T, N> ignored = {};
PaddingField() {}
friend constexpr bool operator==(const PaddingField&, const PaddingField&) {
return true;
}
friend constexpr bool operator<(const PaddingField&, const PaddingField&) {
return false;
}
auto MutTiedFields() { return std::tie(ignored); }
};
} // namespace mozilla
#endif // mozilla_TiedFields_h