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