blob: 589666fdbb6dc87c9443ebf2a743a0ad092af93a [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This file provides facilities to declare compile-time descriptors for C++
// struct types. This enables generic code to access the declared struct
// members in an object.
//
// For example, consider the following struct type:
//
// struct Employee {
// uint32_t id;
// std::string name;
// std::vector<uint32_t> reports;
// };
//
// The descriptor is declared as follows, providing access to |Employee|'s
// members and assigning a unique field number to each of them:
//
// template <>
// struct DescriptorForType<Employee> {
// static constexpr auto kFields =
// MakeFieldList(MakeField(1, &Employee::id),
// MakeField(2, &Employee::name),
// MakeField(3, &Employee::reports));
// };
//
// Note that the |kFields| member is a constexpr, which creates a compile-time
// constant, so the field meta data can be used in compile-time computations and
// as template parameters.
//
// To access the declared members, there is a |Get()| member function template
// on the declared field list, which allows to retrieve one of the field
// specifications by index (zero-based declaration index, *not* field number).
// Once you have the field spec for a field, you can use |FieldSpec::Get()| to
// get a reference to the member within a struct instance. This can be used to
// implement generic algorithms that make use of the descriptor behind the
// scenes. Here is an example that shows how to build a generic comparator:
//
// template <typename Struct>
// struct StructCompare {
// template <typename Member>
// int compareMember(const Member& left, const Member& right) {
// return left < right ? -1 : (right < left ? 1 : 0);
// }
//
// template <size_t... indices>
// int compare(const Struct& left,
// const Struct& right,
// index_sequence<indices...>) {
// constexpr auto kFieldSpecList = DescriptorForType<Struct>::kFields;
// int results[] = {compareMember(
// kFieldSpecList.template Get<indices>().Get(left),
// kFieldSpecList.template Get<indices>().Get(right))...};
// for (int result : results) {
// if (result != 0) {
// return result;
// }
// }
//
// return 0;
// }
//
// bool operator()(const Struct& left, const Struct& right) {
// constexpr auto kFieldSpecList = DescriptorForType<Struct>::kFields;
// return compare(left, right,
// make_index_sequence<kFieldSpecList.kSize>()) < 0;
// }
// };
//
// You can now use |StructCompare| as a key comparison function with std::set
// like this:
//
// std::set<Employee, StructCompare<Employee>> employees;
// employees.emplace(std::move(new_employee));
//
// The ability to write generic algorithms that can process arbitrarily-typed
// struct fields comes at the cost of heavy usage of template constructs.
// However, potential alternatives are not without drawbacks:
// * Avoiding generic code entirely and writing the necessary operations for
// each struct type manually is tedious and error-prone.
// * Tool-generated code is just as hard to comprehend and maintain, and code
// making use of the generated constructs may need to be generated as well.
// * For the intended use in message serialization, there are existing message
// serialization solutions such as protobuf. Unfortunately, our serialization
// code needs to run in resource-constrained environments that don't provide
// a C++ standard library (which is a dependency of the regular protobuf
// implementation), and the library weighs in as a non-trivial dependency in
// terms of code size.
#ifndef NVRAM_MESSAGES_STRUCT_H_
#define NVRAM_MESSAGES_STRUCT_H_
#include <nvram/messages/type_traits.h>
namespace nvram {
// This class template is used to resolve struct types to their corresponding
// descriptors, which provide a list of struct fields that includes the field
// numbers as well as the corresponding C++ struct members in |Struct|. See the
// file comment above for an example.
template <typename Struct>
struct DescriptorForType;
// |FieldSpec| describes a member field of the struct type |Struct|. The
// template parameters capture the C++ |Member| type of the |Struct| member that
// holds the field's data.
//
// Note that this class template is a literal type, i.e. can be used with
// constexpr. As an implication, |FieldSpec| instances can be used as
// compile-time data.
template <typename Struct, typename Member>
struct FieldSpec {
using MemberType = Member;
constexpr FieldSpec(uint32_t field_number, MemberType Struct::* member)
: kFieldNumber(field_number), kMember(member) {}
const MemberType& Get(const Struct& object) const {
return object.*kMember;
}
MemberType& Get(Struct& object) const {
return object.*kMember;
}
// The field number for this field.
const uint32_t kFieldNumber;
// A member pointer to the |Struct| member that holds the field data.
MemberType Struct::*const kMember;
};
// A helper function template that enables template argument deduction to be
// used to construct |FieldSpec| instances.
template <typename Struct, typename Member>
constexpr FieldSpec<Struct, Member> MakeField(uint32_t field_number,
Member Struct::*member) {
return FieldSpec<Struct, Member>(field_number, member);
};
// Forward declaration for |TaggedUnion|, so we don't have to include the full
// header.
template <typename TagType, typename... Member>
class TaggedUnion;
// A special field specification type for protobuf fields belonging to a "oneof"
// construct, of which one field may be active at a time. This is represented by
// a |TaggedUnion| struct member. In addition to the field number and member
// pointer, the field specification also records the |TaggedUnion| tag value
// that selects the |TaggedUnion| member which corresponds to the field.
template <typename Struct, typename TagType, typename... Member>
struct OneOfFieldSpec
: public FieldSpec<Struct, TaggedUnion<TagType, Member...>> {
using TaggedUnionType = TaggedUnion<TagType, Member...>;
constexpr OneOfFieldSpec(uint32_t field_number,
TaggedUnionType Struct::*member,
TagType tag)
: FieldSpec<Struct, TaggedUnionType>(field_number, member), kTag(tag) {}
// The |TaggedUnion| tag corresponding to the |TaggedUnion| member that holds
// the field's data.
const TagType kTag;
};
// A helper function template that simplifies |OneOfFieldSpec| creation by
// enabling template argument type deduction.
template <typename Struct, typename TagType, typename... Member>
constexpr OneOfFieldSpec<Struct, TagType, Member...> MakeOneOfField(
uint32_t field_number,
TaggedUnion<TagType, Member...> Struct::*member,
TagType tag) {
return OneOfFieldSpec<Struct, TagType, Member...>(field_number, member, tag);
};
// A simple type list intended to hold field specification values.
//
// Note that |FieldSpecList| is a literal type so can be used with constexpr to
// hold compile-time data.
template <typename... FieldSpec>
struct FieldSpecList;
namespace {
// A helper template that extracts the field spec at |index| from a field spec
// list.
template <size_t index, typename... FieldSpec>
struct FieldSpecLookup;
// Recursion step: This specialization matches if |index| is larger than 0, and
// the |Get()| definition just forwards to the list tail.
template <size_t index, typename FieldSpec, typename... Tail>
struct FieldSpecLookup<index, FieldSpec, Tail...> {
using Prev = FieldSpecLookup<index - 1, Tail...>;
using Type = typename Prev::Type;
static constexpr Type Get(FieldSpecList<FieldSpec, Tail...> self) {
return Prev::Get(self.kTail);
}
};
// Recursion base case: |index| as reached 0, so |Get()| returns the field spec
// corresponding to the current |FieldSpec|.
template <typename FieldSpec, typename... Tail>
struct FieldSpecLookup<0, FieldSpec, Tail...> {
using Type = FieldSpec;
static constexpr Type Get(FieldSpecList<FieldSpec, Tail...> self) {
return self.kFieldSpec;
}
};
// Produces an error message in case the provided |index| is too large, i.e.
// doesn't match any field. This specialization only matches once the
// |FieldSpec| parameters are exhausted.
template <size_t index>
struct FieldSpecLookup<index> {
// Note that |index < 0| will never be satisfied, so this static assert
// triggers unconditionally if this template specialization ever gets
// instantiated. It will only be instantiated if |index| exceeds the number of
// declared fields.
//
// Just putting |false| as the static_assert condition would seem a saner
// alternative, but doesn't work since the static_assert would then be
// evaluated at declaration time. Using the |index| parameter in the condition
// forces evaluation to take place at template instantiation time.
static_assert(index < 0, "Out-of-bounds |index| in field spec lookup.");
};
} // namespace
// |FieldSpecList| specialization that holds the data of the front-most element
// of |FieldSpecList|'s |Fields| arguments. Note that this class contains a
// nested |FieldSpecList| instance with the front-most element removed, thus
// inheriting the members for subsequent |Fields| arguments.
template <typename FieldSpec, typename... Tail>
struct FieldSpecList<FieldSpec, Tail...> {
using List = FieldSpecList<FieldSpec, Tail...>;
using TailList = FieldSpecList<Tail...>;
constexpr explicit FieldSpecList(FieldSpec field_spec, Tail... tail)
: kFieldSpec(field_spec), kTail(tail...) {}
template <size_t index>
constexpr typename FieldSpecLookup<index, FieldSpec, Tail...>::Type Get()
const {
return FieldSpecLookup<index, FieldSpec, Tail...>::Get(*this);
}
static constexpr size_t kSize = TailList::kSize + 1;
const FieldSpec kFieldSpec;
const TailList kTail;
};
// |FieldSpecList| specialization acting as the recursion base case. This
// doesn't have further members and thus stops the expansion of
// |FieldSpecList|'s |Fields| parameter.
template <>
struct FieldSpecList<> {
static constexpr size_t kSize = 0;
};
// Helper function template that enables convenient creation of |FieldSpecList|
// instances by enabling template argument deduction.
template <typename... FieldSpec>
constexpr FieldSpecList<FieldSpec...> MakeFieldList(FieldSpec... field_spec) {
return FieldSpecList<FieldSpec...>(field_spec...);
}
} // namespace nvram
#endif // NVRAM_MESSAGES_STRUCT_H_