/* 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 #include #include namespace mozilla { /** * TiedFields(T&) -> std::tuple * TiedFields(const T&) -> std::tuple * * You can also overload TiedFields without adding T::MutTiedFields: * template<> * inline auto TiedFields(gfx::IntSize& a) { * return std::tie(a.width, a.height); * } */ template constexpr auto TiedFields(T& t) { return t.MutTiedFields(); } template 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)); return std::apply([](const auto&... f) { return std::tie(f...); }, mutFields); } template struct SizeofTupleArgs; // c++17 fold expressions make this easy, once we pull out the Args parameter // pack by constraining the default template: template struct SizeofTupleArgs> : std::integral_constant {}; /** * 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`. * * See TiedFieldsExamples. */ template constexpr bool AreAllBytesTiedFields() { using fieldsT = decltype(TiedFields(std::declval())); const auto fields_size_sum = SizeofTupleArgs::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 template struct FieldDebugInfoT { static constexpr bool IsTightlyPacked() { return PrevFieldEndOffset % FieldAlignment == 0; } }; template struct TightlyPackedFieldEndOffsetT { template using FieldTAt = std::remove_reference_t< typename std::tuple_element::type>; static constexpr size_t Fn() { constexpr auto num_fields = std::tuple_size_v; static_assert(FieldId < num_fields); using PrevFieldT = FieldTAt; using FieldT = FieldTAt; constexpr auto prev_field_end_offset = TightlyPackedFieldEndOffsetT::Fn(); constexpr auto prev_field_begin_offset = prev_field_end_offset - sizeof(PrevFieldT); using FieldDebugInfoT = FieldDebugInfoT; 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 struct TightlyPackedFieldEndOffsetT { static constexpr size_t Fn() { using FieldT = typename std::tuple_element<0, TupleOfFields>::type; return sizeof(FieldT); } }; template struct TightlyPackedFieldEndOffsetT { static constexpr size_t Fn() { // -1 means tuple_size_v -> 0. static_assert(sizeof(StructT) == 0); return 0; } }; template constexpr bool AssertTiedFieldsAreExhaustive() { static_assert(AreAllBytesTiedFields()); using TupleOfFields = decltype(TiedFields(std::declval())); constexpr auto num_fields = std::tuple_size_v; constexpr auto end_offset_of_last_field = TightlyPackedFieldEndOffsetT::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 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 _padding;` for clarity and to move the `3` nearer * to the `uint8_t`. */ template struct PaddingField { static_assert(!std::is_array_v, "Use PaddingField not ."); std::array 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