mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-02 15:15:23 +00:00
461 lines
13 KiB
C++
461 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* A template class for tagged unions. */
|
|
|
|
#include <new>
|
|
|
|
#include "mozilla/Alignment.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Move.h"
|
|
|
|
#ifndef mozilla_Variant_h
|
|
#define mozilla_Variant_h
|
|
|
|
namespace mozilla {
|
|
|
|
template<typename... Ts>
|
|
class Variant;
|
|
|
|
namespace detail {
|
|
|
|
// MaxSizeOf computes the maximum sizeof(T) for each T in Ts.
|
|
|
|
template<typename T, typename... Ts>
|
|
struct MaxSizeOf
|
|
{
|
|
static const size_t size = sizeof(T) > MaxSizeOf<Ts...>::size
|
|
? sizeof(T)
|
|
: MaxSizeOf<Ts...>::size;
|
|
};
|
|
|
|
template<typename T>
|
|
struct MaxSizeOf<T>
|
|
{
|
|
static const size_t size = sizeof(T);
|
|
};
|
|
|
|
// The `IsVariant` helper is used in conjunction with static_assert and
|
|
// `mozilla::EnableIf` to catch passing non-variant types to `Variant::is<T>()`
|
|
// and friends at compile time, rather than at runtime. It ensures that the
|
|
// given type `Needle` is one of the types in the set of types `Haystack`.
|
|
|
|
template<typename Needle, typename... Haystack>
|
|
struct IsVariant;
|
|
|
|
template<typename Needle>
|
|
struct IsVariant<Needle>
|
|
{
|
|
static const bool value = false;
|
|
};
|
|
|
|
template<typename Needle, typename... Haystack>
|
|
struct IsVariant<Needle, Needle, Haystack...>
|
|
{
|
|
static const bool value = true;
|
|
};
|
|
|
|
template<typename Needle, typename T, typename... Haystack>
|
|
struct IsVariant<Needle, T, Haystack...> : public IsVariant<Needle, Haystack...> { };
|
|
|
|
// TagHelper gets the given sentinel tag value for the given type T. This has to
|
|
// be split out from VariantImplementation because you can't nest a partial template
|
|
// specialization within a template class.
|
|
|
|
template<size_t N, typename T, typename U, typename Next, bool isMatch>
|
|
struct TagHelper;
|
|
|
|
// In the case where T != U, we continue recursion.
|
|
template<size_t N, typename T, typename U, typename Next>
|
|
struct TagHelper<N, T, U, Next, false>
|
|
{
|
|
static size_t tag() { return Next::template tag<U>(); }
|
|
};
|
|
|
|
// In the case where T == U, return the tag number.
|
|
template<size_t N, typename T, typename U, typename Next>
|
|
struct TagHelper<N, T, U, Next, true>
|
|
{
|
|
static size_t tag() { return N; }
|
|
};
|
|
|
|
// The VariantImplementation template provides the guts of mozilla::Variant. We create
|
|
// an VariantImplementation for each T in Ts... which handles construction,
|
|
// destruction, etc for when the Variant's type is T. If the Variant's type is
|
|
// not T, it punts the request on to the next VariantImplementation.
|
|
|
|
template<size_t N, typename... Ts>
|
|
struct VariantImplementation;
|
|
|
|
// The singly typed Variant / recursion base case.
|
|
template<size_t N, typename T>
|
|
struct VariantImplementation<N, T> {
|
|
template<typename U>
|
|
static size_t tag() {
|
|
static_assert(mozilla::IsSame<T, U>::value,
|
|
"mozilla::Variant: tag: bad type!");
|
|
return N;
|
|
}
|
|
|
|
template<typename Variant>
|
|
static void copyConstruct(void* aLhs, const Variant& aRhs) {
|
|
new (aLhs) T(aRhs.template as<T>());
|
|
}
|
|
|
|
template<typename Variant>
|
|
static void moveConstruct(void* aLhs, Variant&& aRhs) {
|
|
new (aLhs) T(aRhs.template extract<T>());
|
|
}
|
|
|
|
template<typename Variant>
|
|
static void destroy(Variant& aV) {
|
|
aV.template as<T>().~T();
|
|
}
|
|
|
|
template<typename Variant>
|
|
static bool
|
|
equal(const Variant& aLhs, const Variant& aRhs) {
|
|
return aLhs.template as<T>() == aRhs.template as<T>();
|
|
}
|
|
|
|
template<typename Matcher, typename ConcreteVariant>
|
|
static typename Matcher::ReturnType
|
|
match(Matcher& aMatcher, ConcreteVariant& aV) {
|
|
return aMatcher.match(aV.template as<T>());
|
|
}
|
|
};
|
|
|
|
// VariantImplementation for some variant type T.
|
|
template<size_t N, typename T, typename... Ts>
|
|
struct VariantImplementation<N, T, Ts...>
|
|
{
|
|
// The next recursive VariantImplementation.
|
|
using Next = VariantImplementation<N + 1, Ts...>;
|
|
|
|
template<typename U>
|
|
static size_t tag() {
|
|
return TagHelper<N, T, U, Next, IsSame<T, U>::value>::tag();
|
|
}
|
|
|
|
template<typename Variant>
|
|
static void copyConstruct(void* aLhs, const Variant& aRhs) {
|
|
if (aRhs.template is<T>()) {
|
|
new (aLhs) T(aRhs.template as<T>());
|
|
} else {
|
|
Next::copyConstruct(aLhs, aRhs);
|
|
}
|
|
}
|
|
|
|
template<typename Variant>
|
|
static void moveConstruct(void* aLhs, Variant&& aRhs) {
|
|
if (aRhs.template is<T>()) {
|
|
new (aLhs) T(aRhs.template extract<T>());
|
|
} else {
|
|
Next::moveConstruct(aLhs, aRhs);
|
|
}
|
|
}
|
|
|
|
template<typename Variant>
|
|
static void destroy(Variant& aV) {
|
|
if (aV.template is<T>()) {
|
|
aV.template as<T>().~T();
|
|
} else {
|
|
Next::destroy(aV);
|
|
}
|
|
}
|
|
|
|
template<typename Variant>
|
|
static bool equal(const Variant& aLhs, const Variant& aRhs) {
|
|
if (aLhs.template is<T>()) {
|
|
MOZ_ASSERT(aRhs.template is<T>());
|
|
return aLhs.template as<T>() == aRhs.template as<T>();
|
|
} else {
|
|
return Next::equal(aLhs, aRhs);
|
|
}
|
|
}
|
|
|
|
template<typename Matcher, typename ConcreteVariant>
|
|
static typename Matcher::ReturnType
|
|
match(Matcher& aMatcher, ConcreteVariant& aV)
|
|
{
|
|
if (aV.template is<T>()) {
|
|
return aMatcher.match(aV.template as<T>());
|
|
} else {
|
|
// If you're seeing compilation errors here like "no matching
|
|
// function for call to 'match'" then that means that the
|
|
// Matcher doesn't exhaust all variant types. There must exist a
|
|
// Matcher::match(T&) for every variant type T.
|
|
//
|
|
// If you're seeing compilation errors here like "cannot
|
|
// initialize return object of type <...> with an rvalue of type
|
|
// <...>" then that means that the Matcher::match(T&) overloads
|
|
// are returning different types. They must all return the same
|
|
// Matcher::ReturnType type.
|
|
return Next::match(aMatcher, aV);
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace detail
|
|
|
|
/**
|
|
* # mozilla::Variant
|
|
*
|
|
* A variant / tagged union / heterogenous disjoint union / sum-type template
|
|
* class. Similar in concept to (but not derived from) `boost::variant`.
|
|
*
|
|
* Sometimes, you may wish to use a C union with non-POD types. However, this is
|
|
* forbidden in C++ because it is not clear which type in the union should have
|
|
* its constructor and destructor run on creation and deletion
|
|
* respectively. This is the problem that `mozilla::Variant` solves.
|
|
*
|
|
* ## Usage
|
|
*
|
|
* A `mozilla::Variant` instance is constructed (via move or copy) from one of
|
|
* its variant types (ignoring const and references). It does *not* support
|
|
* construction from subclasses of variant types or types that coerce to one of
|
|
* the variant types.
|
|
*
|
|
* Variant<char, uint32_t> v1('a');
|
|
* Variant<UniquePtr<A>, B, C> v2(MakeUnique<A>());
|
|
*
|
|
* All access to the contained value goes through type-safe accessors.
|
|
*
|
|
* void
|
|
* Foo(Variant<A, B, C> v)
|
|
* {
|
|
* if (v.is<A>()) {
|
|
* A& ref = v.as<A>();
|
|
* ...
|
|
* } else {
|
|
* ...
|
|
* }
|
|
* }
|
|
*
|
|
* Attempting to use the contained value as type `T1` when the `Variant`
|
|
* instance contains a value of type `T2` causes an assertion failure.
|
|
*
|
|
* A a;
|
|
* Variant<A, B, C> v(a);
|
|
* v.as<B>(); // <--- Assertion failure!
|
|
*
|
|
* Trying to use a `Variant<Ts...>` instance as some type `U` that is not a
|
|
* member of the set of `Ts...` is a compiler error.
|
|
*
|
|
* A a;
|
|
* Variant<A, B, C> v(a);
|
|
* v.as<SomeRandomType>(); // <--- Compiler error!
|
|
*
|
|
* Additionally, you can turn a `Variant` that `is<T>` into a `T` by moving it
|
|
* out of the containing `Variant` instance with the `extract<T>` method:
|
|
*
|
|
* Variant<UniquePtr<A>, B, C> v(MakeUnique<A>());
|
|
* auto ptr = v.extract<UniquePtr<A>>();
|
|
*
|
|
* Finally, you can exhaustively match on the contained variant and branch into
|
|
* different code paths depending which type is contained. This is preferred to
|
|
* manually checking every variant type T with is<T>() because it provides
|
|
* compile-time checking that you handled every type, rather than runtime
|
|
* assertion failures.
|
|
*
|
|
* // Bad!
|
|
* char* foo(Variant<A, B, C, D>& v) {
|
|
* if (v.is<A>()) {
|
|
* return ...;
|
|
* } else if (v.is<B>()) {
|
|
* return ...;
|
|
* } else {
|
|
* return doSomething(v.as<C>()); // Forgot about case D!
|
|
* }
|
|
* }
|
|
*
|
|
* // Good!
|
|
* struct FooMatcher
|
|
* {
|
|
* using ReturnType = char*;
|
|
* ReturnType match(A& a) { ... }
|
|
* ReturnType match(B& b) { ... }
|
|
* ReturnType match(C& c) { ... }
|
|
* ReturnType match(D& d) { ... } // Compile-time error to forget D!
|
|
* }
|
|
* char* foo(Variant<A, B, C, D>& v) {
|
|
* return v.match(FooMatcher());
|
|
* }
|
|
*
|
|
* ## Examples
|
|
*
|
|
* A tree is either an empty leaf, or a node with a value and two children:
|
|
*
|
|
* struct Leaf { };
|
|
*
|
|
* template<typename T>
|
|
* struct Node
|
|
* {
|
|
* T value;
|
|
* Tree<T>* left;
|
|
* Tree<T>* right;
|
|
* };
|
|
*
|
|
* template<typename T>
|
|
* using Tree = Variant<Leaf, Node<T>>;
|
|
*
|
|
* A copy-on-write string is either a non-owning reference to some existing
|
|
* string, or an owning reference to our copy:
|
|
*
|
|
* class CopyOnWriteString
|
|
* {
|
|
* Variant<const char*, UniquePtr<char[]>> string;
|
|
*
|
|
* ...
|
|
* };
|
|
*/
|
|
template<typename... Ts>
|
|
class Variant
|
|
{
|
|
using Impl = detail::VariantImplementation<0, Ts...>;
|
|
using RawData = AlignedStorage<detail::MaxSizeOf<Ts...>::size>;
|
|
|
|
// Each type is given a unique size_t sentinel. This tag lets us keep track of
|
|
// the contained variant value's type.
|
|
size_t tag;
|
|
|
|
// Raw storage for the contained variant value.
|
|
RawData raw;
|
|
|
|
void* ptr() {
|
|
return reinterpret_cast<void*>(&raw);
|
|
}
|
|
|
|
public:
|
|
/** Perfect forwarding construction for some variant type T. */
|
|
template<typename RefT,
|
|
// RefT captures both const& as well as && (as intended, to support
|
|
// perfect forwarding), so we have to remove those qualifiers here
|
|
// when ensuring that T is a variant of this type, and getting T's
|
|
// tag, etc.
|
|
typename T = typename RemoveReference<typename RemoveConst<RefT>::Type>::Type,
|
|
typename = typename EnableIf<detail::IsVariant<T, Ts...>::value, void>::Type>
|
|
explicit Variant(RefT&& aT)
|
|
: tag(Impl::template tag<T>())
|
|
{
|
|
new (ptr()) T(Forward<T>(aT));
|
|
}
|
|
|
|
/** Copy construction. */
|
|
Variant(const Variant& aRhs)
|
|
: tag(aRhs.tag)
|
|
{
|
|
Impl::copyConstruct(ptr(), aRhs);
|
|
}
|
|
|
|
/** Move construction. */
|
|
Variant(Variant&& aRhs)
|
|
: tag(aRhs.tag)
|
|
{
|
|
Impl::moveConstruct(ptr(), Move(aRhs));
|
|
}
|
|
|
|
/** Copy assignment. */
|
|
Variant& operator=(const Variant& aRhs) {
|
|
MOZ_ASSERT(&aRhs != this, "self-assign disallowed");
|
|
this->~Variant();
|
|
new (this) Variant(aRhs);
|
|
return *this;
|
|
}
|
|
|
|
/** Move assignment. */
|
|
Variant& operator=(Variant&& aRhs) {
|
|
MOZ_ASSERT(&aRhs != this, "self-assign disallowed");
|
|
this->~Variant();
|
|
new (this) Variant(Move(aRhs));
|
|
return *this;
|
|
}
|
|
|
|
~Variant()
|
|
{
|
|
Impl::destroy(*this);
|
|
}
|
|
|
|
/** Check which variant type is currently contained. */
|
|
template<typename T>
|
|
bool is() const {
|
|
static_assert(detail::IsVariant<T, Ts...>::value,
|
|
"provided a type not found in this Variant's type list");
|
|
return Impl::template tag<T>() == tag;
|
|
}
|
|
|
|
/**
|
|
* Operator == overload that defers to the variant type's operator==
|
|
* implementation if the rhs is tagged as the same type as this one.
|
|
*/
|
|
bool operator==(const Variant& aRhs) const {
|
|
return tag == aRhs.tag && Impl::equal(*this, aRhs);
|
|
}
|
|
|
|
/**
|
|
* Operator != overload that defers to the negation of the variant type's
|
|
* operator== implementation if the rhs is tagged as the same type as this
|
|
* one.
|
|
*/
|
|
bool operator!=(const Variant& aRhs) const {
|
|
return !(*this == aRhs);
|
|
}
|
|
|
|
// Accessors for working with the contained variant value.
|
|
|
|
/** Mutable reference. */
|
|
template<typename T>
|
|
T& as() {
|
|
static_assert(detail::IsVariant<T, Ts...>::value,
|
|
"provided a type not found in this Variant's type list");
|
|
MOZ_ASSERT(is<T>());
|
|
return *reinterpret_cast<T*>(&raw);
|
|
}
|
|
|
|
/** Immutable const reference. */
|
|
template<typename T>
|
|
const T& as() const {
|
|
static_assert(detail::IsVariant<T, Ts...>::value,
|
|
"provided a type not found in this Variant's type list");
|
|
MOZ_ASSERT(is<T>());
|
|
return *reinterpret_cast<const T*>(&raw);
|
|
}
|
|
|
|
/**
|
|
* Extract the contained variant value from this container into a temporary
|
|
* value. On completion, the value in the variant will be in a
|
|
* safely-destructible state, as determined by the behavior of T's move
|
|
* constructor when provided the variant's internal value.
|
|
*/
|
|
template<typename T>
|
|
T extract() {
|
|
static_assert(detail::IsVariant<T, Ts...>::value,
|
|
"provided a type not found in this Variant's type list");
|
|
MOZ_ASSERT(is<T>());
|
|
return T(Move(as<T>()));
|
|
}
|
|
|
|
// Exhaustive matching of all variant types no the contained value.
|
|
|
|
/** Match on an immutable const reference. */
|
|
template<typename Matcher>
|
|
typename Matcher::ReturnType
|
|
match(Matcher& aMatcher) const {
|
|
return Impl::match(aMatcher, *this);
|
|
}
|
|
|
|
/** Match on a mutable non-const reference. */
|
|
template<typename Matcher>
|
|
typename Matcher::ReturnType
|
|
match(Matcher& aMatcher) {
|
|
return Impl::match(aMatcher, *this);
|
|
}
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif /* mozilla_Variant_h */
|