Bug 1338389 - Index-based Variant::is<N>, as<N>, and extract<N> - r=froydnj draft
authorGerald Squelart <gsquelart@mozilla.com>
Mon, 08 May 2017 11:09:21 +1200
changeset 589407 d88cf614318cc8544d7ab52315e015a7ca4e5efd
parent 589398 897493da0dfac983449da3853fe60bd6ac93117b
child 589408 fed56f264821e87cc474e6686e6dc203c147ac9b
push id62356
push usergsquelart@mozilla.com
push dateTue, 06 Jun 2017 05:57:26 +0000
reviewersfroydnj
bugs1338389
milestone55.0a1
Bug 1338389 - Index-based Variant::is<N>, as<N>, and extract<N> - r=froydnj MozReview-Commit-ID: C5iga0Eb1tH
mfbt/Variant.h
mfbt/tests/TestVariant.cpp
--- a/mfbt/Variant.h
+++ b/mfbt/Variant.h
@@ -20,16 +20,32 @@
 
 namespace mozilla {
 
 template<typename... Ts>
 class Variant;
 
 namespace detail {
 
+// Nth<N, types...>::Type is the Nth type (0-based) in the list of types Ts.
+template<size_t N, typename... Ts>
+struct Nth;
+
+template<typename T, typename... Ts>
+struct Nth<0, T, Ts...>
+{
+  using Type = T;
+};
+
+template<size_t N, typename T, typename... Ts>
+struct Nth<N, T, Ts...>
+{
+  using Type = typename Nth<N - 1, Ts...>::Type;
+};
+
 template <typename...>
 struct FirstTypeIsInRest;
 
 template <typename First>
 struct FirstTypeIsInRest<First> : FalseType {};
 
 template <typename First, typename Second, typename... Rest>
 struct FirstTypeIsInRest<First, Second, Rest...>
@@ -175,41 +191,41 @@ struct VariantImplementation<Tag, N, T>
   static Tag tag() {
     static_assert(mozilla::IsSame<T, U>::value,
                   "mozilla::Variant: tag: bad type!");
     return Tag(N);
   }
 
   template<typename Variant>
   static void copyConstruct(void* aLhs, const Variant& aRhs) {
-    ::new (KnownNotNull, aLhs) T(aRhs.template as<T>());
+    ::new (KnownNotNull, aLhs) T(aRhs.template as<N>());
   }
 
   template<typename Variant>
   static void moveConstruct(void* aLhs, Variant&& aRhs) {
-    ::new (KnownNotNull, aLhs) T(aRhs.template extract<T>());
+    ::new (KnownNotNull, aLhs) T(aRhs.template extract<N>());
   }
 
   template<typename Variant>
   static void destroy(Variant& aV) {
-    aV.template as<T>().~T();
+    aV.template as<N>().~T();
   }
 
   template<typename Variant>
   static bool
   equal(const Variant& aLhs, const Variant& aRhs) {
-      return aLhs.template as<T>() == aRhs.template as<T>();
+      return aLhs.template as<N>() == aRhs.template as<N>();
   }
 
   template<typename Matcher, typename ConcreteVariant>
   static auto
   match(Matcher&& aMatcher, ConcreteVariant& aV)
-    -> decltype(aMatcher.match(aV.template as<T>()))
+    -> decltype(aMatcher.match(aV.template as<N>()))
   {
-    return aMatcher.match(aV.template as<T>());
+    return aMatcher.match(aV.template as<N>());
   }
 };
 
 // VariantImplementation for some variant type T.
 template<typename Tag, size_t N, typename T, typename... Ts>
 struct VariantImplementation<Tag, N, T, Ts...>
 {
   // The next recursive VariantImplementation.
@@ -217,58 +233,58 @@ struct VariantImplementation<Tag, N, T, 
 
   template<typename U>
   static Tag tag() {
     return TagHelper<Tag, 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 (KnownNotNull, aLhs) T(aRhs.template as<T>());
+    if (aRhs.template is<N>()) {
+      ::new (KnownNotNull, aLhs) T(aRhs.template as<N>());
     } else {
       Next::copyConstruct(aLhs, aRhs);
     }
   }
 
   template<typename Variant>
   static void moveConstruct(void* aLhs, Variant&& aRhs) {
-    if (aRhs.template is<T>()) {
-      ::new (KnownNotNull, aLhs) T(aRhs.template extract<T>());
+    if (aRhs.template is<N>()) {
+      ::new (KnownNotNull, aLhs) T(aRhs.template extract<N>());
     } else {
       Next::moveConstruct(aLhs, Move(aRhs));
     }
   }
 
   template<typename Variant>
   static void destroy(Variant& aV) {
-    if (aV.template is<T>()) {
-      aV.template as<T>().~T();
+    if (aV.template is<N>()) {
+      aV.template as<N>().~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>();
+    if (aLhs.template is<N>()) {
+      MOZ_ASSERT(aRhs.template is<N>());
+      return aLhs.template as<N>() == aRhs.template as<N>();
     } else {
       return Next::equal(aLhs, aRhs);
     }
   }
 
   template<typename Matcher, typename ConcreteVariant>
   static auto
   match(Matcher&& aMatcher, ConcreteVariant& aV)
-    -> decltype(aMatcher.match(aV.template as<T>()))
+    -> decltype(aMatcher.match(aV.template as<N>()))
   {
-    if (aV.template is<T>()) {
-      return aMatcher.match(aV.template as<T>());
+    if (aV.template is<N>()) {
+      return aMatcher.match(aV.template as<N>());
     } 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
@@ -344,23 +360,26 @@ struct AsVariantTemporary
  * with primitive or very small types.
  *
  *
  *     Variant<char, uint32_t> Foo() { return AsVariant('x'); }
  *     // ...
  *     Variant<char, uint32_t> v1 = Foo();  // v1 holds char('x').
  *
  * All access to the contained value goes through type-safe accessors.
+ * Either the stored type, or the type index may be provided.
  *
  *     void
  *     Foo(Variant<A, B, C> v)
  *     {
  *       if (v.is<A>()) {
  *         A& ref = v.as<A>();
  *         ...
+ *       } else (v.is<1>()) { // Same as v.is<B> in this case.
+ *         ...
  *       } else {
  *         ...
  *       }
  *     }
  *
  * Attempting to use the contained value as type `T1` when the `Variant`
  * instance contains a value of type `T2` causes an assertion failure.
  *
@@ -377,18 +396,18 @@ struct AsVariantTemporary
  *
  * 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
+ * different code paths depending on 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>()) {
@@ -528,17 +547,17 @@ public:
   Variant& operator=(Variant&& aRhs) {
     MOZ_ASSERT(&aRhs != this, "self-assign disallowed");
     this->~Variant();
     ::new (KnownNotNull, this) Variant(Move(aRhs));
     return *this;
   }
 
   /** Move assignment from AsVariant(). */
-  template <typename T>
+  template<typename T>
   Variant& operator=(detail::AsVariantTemporary<T>&& aValue)
   {
     this->~Variant();
     ::new (KnownNotNull, this) Variant(Move(aValue));
     return *this;
   }
 
   ~Variant()
@@ -549,16 +568,24 @@ public:
   /** 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;
   }
 
+  template<size_t N>
+  bool is() const
+  {
+    static_assert(N < sizeof...(Ts),
+                  "provided an index outside of this Variant's type list");
+    return N == size_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);
   }
 
@@ -577,39 +604,66 @@ public:
   template<typename T>
   T& as() {
     static_assert(detail::IsVariant<T, Ts...>::value,
                   "provided a type not found in this Variant's type list");
     MOZ_RELEASE_ASSERT(is<T>());
     return *static_cast<T*>(ptr());
   }
 
+  template<size_t N>
+  typename detail::Nth<N, Ts...>::Type& as()
+  {
+    static_assert(N < sizeof...(Ts),
+                  "provided an index outside of this Variant's type list");
+    MOZ_RELEASE_ASSERT(is<N>());
+    return *static_cast<typename detail::Nth<N, Ts...>::Type*>(ptr());
+  }
+
   /** 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_RELEASE_ASSERT(is<T>());
     return *static_cast<const T*>(ptr());
   }
 
+  template<size_t N>
+  const typename detail::Nth<N, Ts...>::Type& as() const
+  {
+    static_assert(N < sizeof...(Ts),
+                  "provided an index outside of this Variant's type list");
+    MOZ_RELEASE_ASSERT(is<N>());
+    return *static_cast<const typename detail::Nth<N, Ts...>::Type*>(ptr());
+  }
+
   /**
    * 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>()));
   }
 
+  template<size_t N>
+  typename detail::Nth<N, Ts...>::Type extract()
+  {
+    static_assert(N < sizeof...(Ts),
+                  "provided an index outside of this Variant's type list");
+    MOZ_RELEASE_ASSERT(is<N>());
+    return typename detail::Nth<N, Ts...>::Type(Move(as<N>()));
+  }
+
   // Exhaustive matching of all variant types on the contained value.
 
   /** Match on an immutable const reference. */
   template<typename Matcher>
   auto
   match(Matcher&& aMatcher) const
     -> decltype(Impl::match(aMatcher, *this))
   {
--- a/mfbt/tests/TestVariant.cpp
+++ b/mfbt/tests/TestVariant.cpp
@@ -23,16 +23,22 @@ int Destroyer::destroyedCount = 0;
 static void
 testSimple()
 {
   printf("testSimple\n");
   Variant<uint32_t, uint64_t> v(uint64_t(1));
   MOZ_RELEASE_ASSERT(v.is<uint64_t>());
   MOZ_RELEASE_ASSERT(!v.is<uint32_t>());
   MOZ_RELEASE_ASSERT(v.as<uint64_t>() == 1);
+
+  MOZ_RELEASE_ASSERT(v.is<1>());
+  MOZ_RELEASE_ASSERT(!v.is<0>());
+  static_assert(mozilla::IsSame<decltype(v.as<1>()), uint64_t&>::value,
+                "as<1>() should return a uint64_t");
+  MOZ_RELEASE_ASSERT(v.as<1>() == 1);
 }
 
 static void
 testCopy()
 {
   printf("testCopy\n");
   Variant<uint32_t, uint64_t> v1(uint64_t(1));
   Variant<uint32_t, uint64_t> v2(v1);