blob: 6e1ef57c265b977cbebec18fbe6cec234bd1afd8 [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 defines the |TaggedUnion| class template. It implements a tagged
// union, i.e. it both holds a value of one of a set of pre-determined types, as
// well as an enum value indicating which union member is active. The enum type
// used as tag and the set of union member types a specified as template
// parameters.
//
// For example, consider this declaration of a simple variant type:
//
// enum VariantType {
// Variant_Int = 0,
// Variant_Double = 1,
// Variant_Boolean = 3,
// Variant_String = 2,
// };
//
// using Variant = TaggedUnion<VariantType,
// TaggedUnionMember<Variant_Int, int>,
// TaggedUnionMember<Variant_Double, double>,
// TaggedUnionMember<Variant_Boolean, bool>,
// TaggedUnionMember<Variant_String, std::string>>;
//
// |TaggedUnion::which()| can be used to determine what the active member is,
// and |TaggedUnion::get()| returns a pointer to the member as long as that
// member is active:
//
// Variant value;
// ASSERT_EQ(Variant_Int, value.which());
// ASSERT_NE(nullptr, value.get<Variant_Int>());
// ASSERT_EQ(0, *value.get<Variant_Int>());
//
// |TaggedUnion::Activate()| activates a member. It returns a reference to the
// activated member:
//
// value.Activate<Variant_String>() = "-1";
// ASSERT_EQ(Variant_String, value.which());
//
// To allow generic code to process a |TaggedUnion|, you can use a variant of
// what is commonly known as "the indices trick". The idea is to use template
// parameter pack expansion on |TaggedUnion|'s |Member| parameter to invoke some
// function for each member. For example, the following code determines the
// integer value of a |Variant| value as declared above:
//
// template <typename Type>
// void MemberToInt(const Type* member, int* value) {
// if (member) {
// *value = static_cast<int>(*member);
// }
// }
//
// void MemberToInt(const std::string* member, int* value) {
// if (member) {
// *value = std::stoi(*member);
// }
// }
//
// template <typename... Member>
// int VariantToInt(const TaggedUnion<VariantType, Member...>& variant) {
// int value = 0;
// int dummy[] = {
// (MemberToInt(
// variant.template get<static_cast<VariantType>(Member::kTag)>(),
// &value),
// 0)...};
// (void)dummy;
// return value;
// };
//
// Using this, you can convert a |Variant| in arbitrary state to an integer:
//
// ASSERT_EQ(-1, VariantToInt(value));
#ifndef NVRAM_MESSAGES_TAGGED_UNION_H_
#define NVRAM_MESSAGES_TAGGED_UNION_H_
extern "C" {
#include <stddef.h>
}
#include <new>
#include <nvram/messages/compiler.h>
namespace nvram {
template <uint64_t tag, typename Member>
struct TaggedUnionMember {
static constexpr uint64_t kTag = tag;
using Type = Member;
};
template <typename TagType, typename... Members>
class TaggedUnion;
namespace detail {
// A compile-time maximum implementation.
template <size_t... Values>
struct Max;
template <>
struct Max<> {
static constexpr size_t value = 0;
};
template <size_t head, size_t... tail>
struct Max<head, tail...> {
static constexpr size_t value =
head > Max<tail...>::value ? head : Max<tail...>::value;
};
// A helper template that determines the |TaggedUnionMember| type corresponding
// to |tag| via recursive expansion of the |Member| parameter list.
template <typename TagType, TagType tag, typename... Member>
struct MemberForTag;
template <typename TagType,
TagType tag,
uint64_t member_tag,
typename MemberType,
typename... Tail>
struct MemberForTag<TagType,
tag,
TaggedUnionMember<member_tag, MemberType>,
Tail...> {
using Type = typename MemberForTag<TagType, tag, Tail...>::Type;
};
template <typename TagType, TagType tag, typename MemberType, typename... Tail>
struct MemberForTag<TagType,
tag,
TaggedUnionMember<static_cast<uint64_t>(tag), MemberType>,
Tail...> {
using Type = TaggedUnionMember<tag, MemberType>;
};
// Extracts the first element of its template parameter list.
template <typename Elem, typename...Tail>
struct Head {
using Type = Elem;
};
} // namespace detail
template <typename TagType, typename... Member>
class TaggedUnion {
public:
template <TagType tag>
struct MemberLookup {
using Type = typename detail::MemberForTag<TagType, tag, Member...>::Type;
};
// Construct a |TaggedUnion| object. Note that the constructor will activate
// the first declared union member.
TaggedUnion() {
Construct<static_cast<TagType>(detail::Head<Member...>::Type::kTag)>();
}
~TaggedUnion() {
Destroy();
}
// |TaggedUnion| is copyable and movable, provided the members have suitable
// copy and move assignment operators.
TaggedUnion(const TaggedUnion<TagType, Member...>& other) {
CopyFrom(other);
}
TaggedUnion(TaggedUnion<TagType, Member...>&& other) {
MoveFrom(other);
}
TaggedUnion<TagType, Member...>& operator=(
const TaggedUnion<TagType, Member...>& other) {
CopyFrom(other);
}
TaggedUnion<TagType, Member...>& operator=(
TaggedUnion<TagType, Member...>&& other) {
MoveFrom(other);
}
// Returns the tag value corresponding to the active member.
TagType which() const { return which_; }
// Get a pointer to the member corresponding to |tag|. Returns nullptr if
// |tag| doesn't correspond to the active member.
template <TagType tag>
const typename MemberLookup<tag>::Type::Type* get() const {
return which_ == tag ? GetUnchecked<tag>() : nullptr;
}
// Get a pointer to the member corresponding to |tag|. Returns nullptr if
// |tag| doesn't correspond to the active member.
template <TagType tag>
typename MemberLookup<tag>::Type::Type* get() {
return which_ == tag ? GetUnchecked<tag>() : nullptr;
}
// Activate the member identified by |tag|. First, the currently active member
// will be destroyed. Then, the member corresponding to |tag| will be
// constructed (i.e. value-initialized). Returns a reference to the activated
// member.
template <TagType tag>
typename MemberLookup<tag>::Type::Type& Activate() {
Destroy();
Construct<tag>();
return *GetUnchecked<tag>();
}
private:
template<TagType tag>
const typename MemberLookup<tag>::Type::Type* GetUnchecked() const {
return reinterpret_cast<const typename MemberLookup<tag>::Type::Type*>(
storage_);
}
template<TagType tag>
typename MemberLookup<tag>::Type::Type* GetUnchecked() {
return reinterpret_cast<typename MemberLookup<tag>::Type::Type*>(storage_);
}
template <TagType tag>
void Construct() {
using MemberType = typename MemberLookup<tag>::Type::Type;
new (storage_) MemberType();
which_ = tag;
}
template <typename CurrentMember>
void DestroyMember() {
using MemberType = typename CurrentMember::Type;
if (CurrentMember::kTag == which_) {
GetUnchecked<static_cast<TagType>(CurrentMember::kTag)>()->~MemberType();
}
}
// This is marked noinline to prevent bloat due to the compiler inlining
// |Destroy()| into each instance of the |Activate()| function.
NVRAM_NOINLINE void Destroy() {
int dummy[] = {(DestroyMember<Member>(), 0)...};
(void)dummy;
}
template <typename CurrentMember>
void CopyMember(const typename CurrentMember::Type* member) {
if (member) {
if (CurrentMember::kTag != which_) {
Activate<CurrentMember::kTag>();
}
*GetUnchecked<static_cast<TagType>(CurrentMember::kTag)>() = *member;
}
}
NVRAM_NOINLINE void CopyFrom(const TaggedUnion<TagType, Member...>& other) {
int dummy[] = {
(CopyMember<Member>(
other.template get<static_cast<TagType>(Member::kTag)>()),
0)...};
(void)dummy;
}
template <typename CurrentMember>
void MoveMember(const typename CurrentMember::Type* member) {
if (member) {
if (CurrentMember::kTag != which_) {
Activate<CurrentMember::kTag>();
}
*GetUnchecked<static_cast<TagType>(CurrentMember::kTag)>() =
static_cast<typename CurrentMember::Type&&>(*member);
}
}
NVRAM_NOINLINE void MoveFrom(const TaggedUnion<TagType, Member...>& other) {
int dummy[] = {
(MoveMember<Member>(
other.template get<static_cast<TagType>(Member::kTag)>()),
0)...};
(void)dummy;
}
// The + 0 is required to work around a G++ bug:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55382
alignas(detail::Max<alignof(typename Member::Type)...>::value + 0)
uint8_t storage_[detail::Max<sizeof(typename Member::Type)...>::value];
TagType which_;
};
} // namespace nvram
#endif // NVRAM_MESSAGES_TAGGED_UNION_H_