1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // This file provides facilities to declare compile-time descriptors for C++
18 // struct types. This enables generic code to access the declared struct
19 // members in an object.
20 //
21 // For example, consider the following struct type:
22 //
23 // struct Employee {
24 // uint32_t id;
25 // std::string name;
26 // std::vector<uint32_t> reports;
27 // };
28 //
29 // The descriptor is declared as follows, providing access to |Employee|'s
30 // members and assigning a unique field number to each of them:
31 //
32 // template <>
33 // struct DescriptorForType<Employee> {
34 // static constexpr auto kFields =
35 // MakeFieldList(MakeField(1, &Employee::id),
36 // MakeField(2, &Employee::name),
37 // MakeField(3, &Employee::reports));
38 // };
39 //
40 // Note that the |kFields| member is a constexpr, which creates a compile-time
41 // constant, so the field meta data can be used in compile-time computations and
42 // as template parameters.
43 //
44 // To access the declared members, there is a |Get()| member function template
45 // on the declared field list, which allows to retrieve one of the field
46 // specifications by index (zero-based declaration index, *not* field number).
47 // Once you have the field spec for a field, you can use |FieldSpec::Get()| to
48 // get a reference to the member within a struct instance. This can be used to
49 // implement generic algorithms that make use of the descriptor behind the
50 // scenes. Here is an example that shows how to build a generic comparator:
51 //
52 // template <typename Struct>
53 // struct StructCompare {
54 // template <typename Member>
55 // int compareMember(const Member& left, const Member& right) {
56 // return left < right ? -1 : (right < left ? 1 : 0);
57 // }
58 //
59 // template <size_t... indices>
60 // int compare(const Struct& left,
61 // const Struct& right,
62 // index_sequence<indices...>) {
63 // constexpr auto kFieldSpecList = DescriptorForType<Struct>::kFields;
64 // int results[] = {compareMember(
65 // kFieldSpecList.template Get<indices>().Get(left),
66 // kFieldSpecList.template Get<indices>().Get(right))...};
67 // for (int result : results) {
68 // if (result != 0) {
69 // return result;
70 // }
71 // }
72 //
73 // return 0;
74 // }
75 //
76 // bool operator()(const Struct& left, const Struct& right) {
77 // constexpr auto kFieldSpecList = DescriptorForType<Struct>::kFields;
78 // return compare(left, right,
79 // make_index_sequence<kFieldSpecList.kSize>()) < 0;
80 // }
81 // };
82 //
83 // You can now use |StructCompare| as a key comparison function with std::set
84 // like this:
85 //
86 // std::set<Employee, StructCompare<Employee>> employees;
87 // employees.emplace(std::move(new_employee));
88 //
89 // The ability to write generic algorithms that can process arbitrarily-typed
90 // struct fields comes at the cost of heavy usage of template constructs.
91 // However, potential alternatives are not without drawbacks:
92 // * Avoiding generic code entirely and writing the necessary operations for
93 // each struct type manually is tedious and error-prone.
94 // * Tool-generated code is just as hard to comprehend and maintain, and code
95 // making use of the generated constructs may need to be generated as well.
96 // * For the intended use in message serialization, there are existing message
97 // serialization solutions such as protobuf. Unfortunately, our serialization
98 // code needs to run in resource-constrained environments that don't provide
99 // a C++ standard library (which is a dependency of the regular protobuf
100 // implementation), and the library weighs in as a non-trivial dependency in
101 // terms of code size.
102
103 #ifndef NVRAM_MESSAGES_STRUCT_H_
104 #define NVRAM_MESSAGES_STRUCT_H_
105
106 #include <nvram/messages/type_traits.h>
107
108 namespace nvram {
109
110 // This class template is used to resolve struct types to their corresponding
111 // descriptors, which provide a list of struct fields that includes the field
112 // numbers as well as the corresponding C++ struct members in |Struct|. See the
113 // file comment above for an example.
114 template <typename Struct>
115 struct DescriptorForType;
116
117 // |FieldSpec| describes a member field of the struct type |Struct|. The
118 // template parameters capture the C++ |Member| type of the |Struct| member that
119 // holds the field's data.
120 //
121 // Note that this class template is a literal type, i.e. can be used with
122 // constexpr. As an implication, |FieldSpec| instances can be used as
123 // compile-time data.
124 template <typename Struct, typename Member>
125 struct FieldSpec {
126 using MemberType = Member;
127
FieldSpecFieldSpec128 constexpr FieldSpec(uint32_t field_number, MemberType Struct::* member)
129 : kFieldNumber(field_number), kMember(member) {}
130
GetFieldSpec131 const MemberType& Get(const Struct& object) const {
132 return object.*kMember;
133 }
134
GetFieldSpec135 MemberType& Get(Struct& object) const {
136 return object.*kMember;
137 }
138
139 // The field number for this field.
140 const uint32_t kFieldNumber;
141
142 // A member pointer to the |Struct| member that holds the field data.
143 MemberType Struct::*const kMember;
144 };
145
146 // A helper function template that enables template argument deduction to be
147 // used to construct |FieldSpec| instances.
148 template <typename Struct, typename Member>
MakeField(uint32_t field_number,Member Struct::* member)149 constexpr FieldSpec<Struct, Member> MakeField(uint32_t field_number,
150 Member Struct::*member) {
151 return FieldSpec<Struct, Member>(field_number, member);
152 };
153
154 // Forward declaration for |TaggedUnion|, so we don't have to include the full
155 // header.
156 template <typename TagType, typename... Member>
157 class TaggedUnion;
158
159 // A special field specification type for protobuf fields belonging to a "oneof"
160 // construct, of which one field may be active at a time. This is represented by
161 // a |TaggedUnion| struct member. In addition to the field number and member
162 // pointer, the field specification also records the |TaggedUnion| tag value
163 // that selects the |TaggedUnion| member which corresponds to the field.
164 template <typename Struct, typename TagType, typename... Member>
165 struct OneOfFieldSpec
166 : public FieldSpec<Struct, TaggedUnion<TagType, Member...>> {
167 using TaggedUnionType = TaggedUnion<TagType, Member...>;
168
OneOfFieldSpecOneOfFieldSpec169 constexpr OneOfFieldSpec(uint32_t field_number,
170 TaggedUnionType Struct::*member,
171 TagType tag)
172 : FieldSpec<Struct, TaggedUnionType>(field_number, member), kTag(tag) {}
173
174 // The |TaggedUnion| tag corresponding to the |TaggedUnion| member that holds
175 // the field's data.
176 const TagType kTag;
177 };
178
179 // A helper function template that simplifies |OneOfFieldSpec| creation by
180 // enabling template argument type deduction.
181 template <typename Struct, typename TagType, typename... Member>
MakeOneOfField(uint32_t field_number,TaggedUnion<TagType,Member...> Struct::* member,TagType tag)182 constexpr OneOfFieldSpec<Struct, TagType, Member...> MakeOneOfField(
183 uint32_t field_number,
184 TaggedUnion<TagType, Member...> Struct::*member,
185 TagType tag) {
186 return OneOfFieldSpec<Struct, TagType, Member...>(field_number, member, tag);
187 };
188
189 // A simple type list intended to hold field specification values.
190 //
191 // Note that |FieldSpecList| is a literal type so can be used with constexpr to
192 // hold compile-time data.
193 template <typename... FieldSpec>
194 struct FieldSpecList;
195
196 namespace {
197
198 // A helper template that extracts the field spec at |index| from a field spec
199 // list.
200 template <size_t index, typename... FieldSpec>
201 struct FieldSpecLookup;
202
203 // Recursion step: This specialization matches if |index| is larger than 0, and
204 // the |Get()| definition just forwards to the list tail.
205 template <size_t index, typename FieldSpec, typename... Tail>
206 struct FieldSpecLookup<index, FieldSpec, Tail...> {
207 using Prev = FieldSpecLookup<index - 1, Tail...>;
208 using Type = typename Prev::Type;
209 static constexpr Type Get(FieldSpecList<FieldSpec, Tail...> self) {
210 return Prev::Get(self.kTail);
211 }
212 };
213
214 // Recursion base case: |index| as reached 0, so |Get()| returns the field spec
215 // corresponding to the current |FieldSpec|.
216 template <typename FieldSpec, typename... Tail>
217 struct FieldSpecLookup<0, FieldSpec, Tail...> {
218 using Type = FieldSpec;
219 static constexpr Type Get(FieldSpecList<FieldSpec, Tail...> self) {
220 return self.kFieldSpec;
221 }
222 };
223
224 // Produces an error message in case the provided |index| is too large, i.e.
225 // doesn't match any field. This specialization only matches once the
226 // |FieldSpec| parameters are exhausted.
227 template <size_t index>
228 struct FieldSpecLookup<index> {
229 // Note that |index < 0| will never be satisfied, so this static assert
230 // triggers unconditionally if this template specialization ever gets
231 // instantiated. It will only be instantiated if |index| exceeds the number of
232 // declared fields.
233 //
234 // Just putting |false| as the static_assert condition would seem a saner
235 // alternative, but doesn't work since the static_assert would then be
236 // evaluated at declaration time. Using the |index| parameter in the condition
237 // forces evaluation to take place at template instantiation time.
238 static_assert(index < 0, "Out-of-bounds |index| in field spec lookup.");
239 };
240
241 } // namespace
242
243 // |FieldSpecList| specialization that holds the data of the front-most element
244 // of |FieldSpecList|'s |Fields| arguments. Note that this class contains a
245 // nested |FieldSpecList| instance with the front-most element removed, thus
246 // inheriting the members for subsequent |Fields| arguments.
247 template <typename FieldSpec, typename... Tail>
248 struct FieldSpecList<FieldSpec, Tail...> {
249 using List = FieldSpecList<FieldSpec, Tail...>;
250 using TailList = FieldSpecList<Tail...>;
251
252 constexpr explicit FieldSpecList(FieldSpec field_spec, Tail... tail)
253 : kFieldSpec(field_spec), kTail(tail...) {}
254
255 template <size_t index>
256 constexpr typename FieldSpecLookup<index, FieldSpec, Tail...>::Type Get()
257 const {
258 return FieldSpecLookup<index, FieldSpec, Tail...>::Get(*this);
259 }
260
261 static constexpr size_t kSize = TailList::kSize + 1;
262 const FieldSpec kFieldSpec;
263 const TailList kTail;
264 };
265
266 // |FieldSpecList| specialization acting as the recursion base case. This
267 // doesn't have further members and thus stops the expansion of
268 // |FieldSpecList|'s |Fields| parameter.
269 template <>
270 struct FieldSpecList<> {
271 static constexpr size_t kSize = 0;
272 };
273
274 // Helper function template that enables convenient creation of |FieldSpecList|
275 // instances by enabling template argument deduction.
276 template <typename... FieldSpec>
277 constexpr FieldSpecList<FieldSpec...> MakeFieldList(FieldSpec... field_spec) {
278 return FieldSpecList<FieldSpec...>(field_spec...);
279 }
280
281 } // namespace nvram
282
283 #endif // NVRAM_MESSAGES_STRUCT_H_
284