tagged_union.h revision a715cb1840f9a0c813c90707a351687f7a77950e
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 defines the |TaggedUnion| class template. It implements a tagged
18// union, i.e. it both holds a value of one of a set of pre-determined types, as
19// well as an enum value indicating which union member is active. The enum type
20// used as tag and the set of union member types a specified as template
21// parameters.
22//
23// For example, consider this declaration of a simple variant type:
24//
25//   enum VariantType {
26//     Variant_Int = 0,
27//     Variant_Double = 1,
28//     Variant_Boolean = 3,
29//     Variant_String = 2,
30//   };
31//
32//   using Variant = TaggedUnion<VariantType,
33//                        TaggedUnionMember<Variant_Int, int>,
34//                        TaggedUnionMember<Variant_Double, double>,
35//                        TaggedUnionMember<Variant_Boolean, bool>,
36//                        TaggedUnionMember<Variant_String, std::string>>;
37//
38// |TaggedUnion::which()| can be used to determine what the active member is,
39// and |TaggedUnion::get()| returns a pointer to the member as long as that
40// member is active:
41//
42//   Variant value;
43//   ASSERT_EQ(Variant_Int, value.which());
44//   ASSERT_NE(nullptr, value.get<Variant_Int>());
45//   ASSERT_EQ(0, *value.get<Variant_Int>());
46//
47// |TaggedUnion::Activate()| activates a member. It returns a reference to the
48// activated member:
49//
50//   value.Activate<Variant_String>() = "-1";
51//   ASSERT_EQ(Variant_String, value.which());
52//
53// To allow generic code to process a |TaggedUnion|, you can use a variant of
54// what is commonly known as "the indices trick". The idea is to use template
55// parameter pack expansion on |TaggedUnion|'s |Member| parameter to invoke some
56// function for each member. For example, the following code determines the
57// integer value of a |Variant| value as declared above:
58//
59//   template <typename Type>
60//   void MemberToInt(const Type* member, int* value) {
61//     if (member) {
62//       *value = static_cast<int>(*member);
63//     }
64//   }
65//
66//   void MemberToInt(const std::string* member, int* value) {
67//     if (member) {
68//       *value = std::stoi(*member);
69//     }
70//   }
71//
72//   template <typename... Member>
73//   int VariantToInt(const TaggedUnion<VariantType, Member...>& variant) {
74//     int value = 0;
75//     int dummy[] = {
76//         (MemberToInt(
77//              variant.template get<static_cast<VariantType>(Member::kTag)>(),
78//              &value),
79//          0)...};
80//     (void)dummy;
81//     return value;
82//   };
83//
84// Using this, you can convert a |Variant| in arbitrary state to an integer:
85//
86//   ASSERT_EQ(-1, VariantToInt(value));
87
88#ifndef NVRAM_MESSAGES_TAGGED_UNION_H_
89#define NVRAM_MESSAGES_TAGGED_UNION_H_
90
91extern "C" {
92#include <stddef.h>
93}
94
95#include <new>
96
97#include <nvram/messages/compiler.h>
98
99namespace nvram {
100
101template <uint64_t tag, typename Member>
102struct TaggedUnionMember {
103  static constexpr uint64_t kTag = tag;
104  using Type = Member;
105};
106
107template <typename TagType, typename... Members>
108class TaggedUnion;
109
110namespace detail {
111
112// A compile-time maximum implementation.
113template <size_t... Values>
114struct Max;
115
116template <>
117struct Max<> {
118  static constexpr size_t value = 0;
119};
120
121template <size_t head, size_t... tail>
122struct Max<head, tail...> {
123  static constexpr size_t value =
124      head > Max<tail...>::value ? head : Max<tail...>::value;
125};
126
127// A helper template that determines the |TaggedUnionMember| type corresponding
128// to |tag| via recursive expansion of the |Member| parameter list.
129template <typename TagType, TagType tag, typename... Member>
130struct MemberForTag;
131
132template <typename TagType,
133          TagType tag,
134          uint64_t member_tag,
135          typename MemberType,
136          typename... Tail>
137struct MemberForTag<TagType,
138                    tag,
139                    TaggedUnionMember<member_tag, MemberType>,
140                    Tail...> {
141  using Type = typename MemberForTag<TagType, tag, Tail...>::Type;
142};
143
144template <typename TagType, TagType tag, typename MemberType, typename... Tail>
145struct MemberForTag<TagType,
146                    tag,
147                    TaggedUnionMember<static_cast<uint64_t>(tag), MemberType>,
148                    Tail...> {
149  using Type = TaggedUnionMember<tag, MemberType>;
150};
151
152// Extracts the first element of its template parameter list.
153template <typename Elem, typename...Tail>
154struct Head {
155  using Type = Elem;
156};
157
158}  // namespace detail
159
160template <typename TagType, typename... Member>
161class TaggedUnion {
162 public:
163  template <TagType tag>
164  struct MemberLookup {
165    using Type = typename detail::MemberForTag<TagType, tag, Member...>::Type;
166  };
167
168  // Construct a |TaggedUnion| object. Note that the constructor will activate
169  // the first declared union member.
170  TaggedUnion() {
171    Construct<static_cast<TagType>(detail::Head<Member...>::Type::kTag)>();
172  }
173
174  ~TaggedUnion() {
175    Destroy();
176  }
177
178  // |TaggedUnion| is copyable and movable, provided the members have suitable
179  // copy and move assignment operators.
180  TaggedUnion(const TaggedUnion<TagType, Member...>& other) {
181    CopyFrom(other);
182  }
183  TaggedUnion(TaggedUnion<TagType, Member...>&& other) {
184    MoveFrom(other);
185  }
186  TaggedUnion<TagType, Member...>& operator=(
187      const TaggedUnion<TagType, Member...>& other) {
188    CopyFrom(other);
189  }
190  TaggedUnion<TagType, Member...>& operator=(
191      TaggedUnion<TagType, Member...>&& other) {
192    MoveFrom(other);
193  }
194
195  // Returns the tag value corresponding to the active member.
196  TagType which() const { return which_; }
197
198  // Get a pointer to the member corresponding to |tag|. Returns nullptr if
199  // |tag| doesn't correspond to the active member.
200  template <TagType tag>
201  const typename MemberLookup<tag>::Type::Type* get() const {
202    return which_ == tag ? GetUnchecked<tag>() : nullptr;
203  }
204
205  // Get a pointer to the member corresponding to |tag|. Returns nullptr if
206  // |tag| doesn't correspond to the active member.
207  template <TagType tag>
208  typename MemberLookup<tag>::Type::Type* get() {
209    return which_ == tag ? GetUnchecked<tag>() : nullptr;
210  }
211
212  // Activate the member identified by |tag|. First, the currently active member
213  // will be destroyed. Then, the member corresponding to |tag| will be
214  // constructed (i.e. value-initialized). Returns a reference to the activated
215  // member.
216  template <TagType tag>
217  typename MemberLookup<tag>::Type::Type& Activate() {
218    Destroy();
219    Construct<tag>();
220    return *GetUnchecked<tag>();
221  }
222
223 private:
224  template<TagType tag>
225  const typename MemberLookup<tag>::Type::Type* GetUnchecked() const {
226    return reinterpret_cast<const typename MemberLookup<tag>::Type::Type*>(
227        storage_);
228  }
229
230  template<TagType tag>
231  typename MemberLookup<tag>::Type::Type* GetUnchecked() {
232    return reinterpret_cast<typename MemberLookup<tag>::Type::Type*>(storage_);
233  }
234
235  template <TagType tag>
236  void Construct() {
237    using MemberType = typename MemberLookup<tag>::Type::Type;
238    new (storage_) MemberType();
239    which_ = tag;
240  }
241
242  template <typename CurrentMember>
243  void DestroyMember() {
244    using MemberType = typename CurrentMember::Type;
245    if (CurrentMember::kTag == which_) {
246      GetUnchecked<static_cast<TagType>(CurrentMember::kTag)>()->~MemberType();
247    }
248  }
249
250  // This is marked noinline to prevent bloat due to the compiler inlining
251  // |Destroy()| into each instance of the |Activate()| function.
252  NVRAM_NOINLINE void Destroy() {
253    int dummy[] = {(DestroyMember<Member>(), 0)...};
254    (void)dummy;
255  }
256
257  template <typename CurrentMember>
258  void CopyMember(const typename CurrentMember::Type* member) {
259    if (member) {
260      if (CurrentMember::kTag != which_) {
261        Activate<CurrentMember::kTag>();
262      }
263      *GetUnchecked<static_cast<TagType>(CurrentMember::kTag)>() = *member;
264    }
265  }
266
267  NVRAM_NOINLINE void CopyFrom(const TaggedUnion<TagType, Member...>& other) {
268    int dummy[] = {
269        (CopyMember<Member>(
270             other.template get<static_cast<TagType>(Member::kTag)>()),
271         0)...};
272    (void)dummy;
273  }
274
275  template <typename CurrentMember>
276  void MoveMember(const typename CurrentMember::Type* member) {
277    if (member) {
278      if (CurrentMember::kTag != which_) {
279        Activate<CurrentMember::kTag>();
280      }
281      *GetUnchecked<static_cast<TagType>(CurrentMember::kTag)>() =
282          static_cast<typename CurrentMember::Type&&>(*member);
283    }
284  }
285
286  NVRAM_NOINLINE void MoveFrom(const TaggedUnion<TagType, Member...>& other) {
287    int dummy[] = {
288        (MoveMember<Member>(
289             other.template get<static_cast<TagType>(Member::kTag)>()),
290         0)...};
291    (void)dummy;
292  }
293
294  // The + 0 is required to work around a G++ bug:
295  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55382
296  alignas(detail::Max<alignof(typename Member::Type)...>::value + 0)
297      uint8_t storage_[detail::Max<sizeof(typename Member::Type)...>::value];
298  TagType which_;
299};
300
301}  // namespace nvram
302
303#endif  // NVRAM_MESSAGES_TAGGED_UNION_H_
304