Revision 87da35989ab5699d5dbcdfa65b2822a0edf67c50 authored by Jeff Gilbert on 18 February 2016, 22:39:26 UTC, committed by Jeff Gilbert on 18 February 2016, 22:39:26 UTC
1 parent a6d52b3
Raw File
Variant.h
/* -*- 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 */
back to top