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 128 constexpr FieldSpec(uint32_t field_number, MemberType Struct::* member) 129 : kFieldNumber(field_number), kMember(member) {} 130 131 const MemberType& Get(const Struct& object) const { 132 return object.*kMember; 133 } 134 135 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> 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 169 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> 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