Bug 1338389 - Index-based Variant::is<N>, as<N>, and extract<N> - r=froydnj
authorGerald Squelart <gsquelart@mozilla.com>
Mon, 08 May 2017 11:09:21 +1200
changeset 362765 d0254e59eb1078f06d7ebb19d7751525e3bd56a9
parent 362764 12af2c27c2b2bb6dd55dea06f0364b564b27e26a
child 362766 1a8ecb297f243f4bc1bc055d5478758ddf57d0fa
push id31988
push usercbook@mozilla.com
push dateThu, 08 Jun 2017 12:43:02 +0000
treeherdermozilla-central@f223e1fd2044 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1338389
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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);