Optimize tint::IsAnyOf<>() for many types

Split IsAnyOf() into log(n) stages, where each stage performs a hashcode
check.

Previously there was a single hash test across the bitwise-or of all the
types being considered. If this passed, then each type would be tested
with Is<T>() individually. With this change, the list of types will be
recursively split into two, which each block hash-code checked. This is
repeated until we reach fewer than 4 types to check, where the test
decays to using Is<T>() for each type.

Also renamed `combined_hashcode` to `full_hashcode`, and used the term
CombinedHash for new helpers that bitwise-or the hashes from a number
of types.

Bug: tint:1383
Change-Id: Id056b9f7a9792430bd75ce554cb5fe73221ca4c7
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/78580
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Ben Clayton <bclayton@google.com>
diff --git a/src/castable.cc b/src/castable.cc
index e63981f..02c3ebc 100644
--- a/src/castable.cc
+++ b/src/castable.cc
@@ -23,7 +23,7 @@
     nullptr,
     "CastableBase",
     tint::TypeInfo::HashCodeOf<CastableBase>(),
-    tint::TypeInfo::HashCodeOf<CastableBase>(),
+    tint::TypeInfo::FullHashCodeOf<CastableBase>(),
 };
 
 }  // namespace tint
diff --git a/src/castable.h b/src/castable.h
index 280cc17..3104492 100644
--- a/src/castable.h
+++ b/src/castable.h
@@ -17,6 +17,7 @@
 
 #include <stdint.h>
 #include <functional>
+#include <tuple>
 #include <utility>
 
 #include "src/traits.h"
@@ -61,7 +62,7 @@
       &tint::detail::TypeInfoOf<CLASS::TrueBase>::info,       \
       #CLASS,                                                 \
       tint::TypeInfo::HashCodeOf<CLASS>(),                    \
-      tint::TypeInfo::CombinedHashCodeOf<CLASS>(),            \
+      tint::TypeInfo::FullHashCodeOf<CLASS>(),                \
   };                                                          \
   TINT_CASTABLE_POP_DISABLE_WARNINGS()
 
@@ -86,17 +87,17 @@
   const char* name;
   /// The type hash code
   const HashCode hashcode;
-  /// The type hash code or'd with the base class' combined hash code
-  const HashCode combined_hashcode;
+  /// The type hash code bitwise-or'd with all ancestor's hashcodes.
+  const HashCode full_hashcode;
 
   /// @param type the test type info
   /// @returns true if the class with this TypeInfo is of, or derives from the
   /// class with the given TypeInfo.
   inline bool Is(const tint::TypeInfo* type) const {
     // Optimization: Check whether the all the bits of the type's hashcode can
-    // be found in the combined_hashcode. If a single bit is missing, then we
+    // be found in the full_hashcode. If a single bit is missing, then we
     // can quickly tell that that this TypeInfo does not derive from `type`.
-    if ((combined_hashcode & type->hashcode) != type->hashcode) {
+    if ((full_hashcode & type->hashcode) != type->hashcode) {
       return false;
     }
 
@@ -145,6 +146,8 @@
   /// multiple hashcodes are bitwise-or'd together.
   template <typename T>
   static constexpr HashCode HashCodeOf() {
+    static_assert(traits::IsTypeOrDerived<T, CastableBase>::value,
+                  "T is not Castable");
     /// Use the compiler's "pretty" function name, which includes the template
     /// type, to obtain a unique hash value.
 #ifdef _MSC_VER
@@ -161,13 +164,75 @@
   /// @returns the hashcode of the given type, bitwise-or'd with the hashcodes
   /// of all base classes.
   template <typename T>
-  static constexpr HashCode CombinedHashCodeOf() {
+  static constexpr HashCode FullHashCodeOf() {
     if constexpr (std::is_same_v<T, CastableBase>) {
       return HashCodeOf<CastableBase>();
     } else {
-      return HashCodeOf<T>() | CombinedHashCodeOf<typename T::TrueBase>();
+      return HashCodeOf<T>() | FullHashCodeOf<typename T::TrueBase>();
     }
   }
+
+  /// @returns the bitwise-or'd hashcodes of all the types of the tuple `TUPLE`.
+  /// @see HashCodeOf
+  template <typename TUPLE>
+  static constexpr HashCode CombinedHashCodeOfTuple() {
+    constexpr auto kCount = std::tuple_size_v<TUPLE>;
+    if constexpr (kCount == 0) {
+      return 0;
+    } else if constexpr (kCount == 1) {
+      return HashCodeOf<std::tuple_element_t<0, TUPLE>>();
+    } else {
+      constexpr auto kMid = kCount / 2;
+      return CombinedHashCodeOfTuple<traits::SliceTuple<0, kMid, TUPLE>>() |
+             CombinedHashCodeOfTuple<
+                 traits::SliceTuple<kMid, kCount - kMid, TUPLE>>();
+    }
+  }
+
+  /// @returns the bitwise-or'd hashcodes of all the template parameter types.
+  /// @see HashCodeOf
+  template <typename... TYPES>
+  static constexpr HashCode CombinedHashCodeOf() {
+    return CombinedHashCodeOfTuple<std::tuple<TYPES...>>();
+  }
+
+  /// @returns true if this TypeInfo is of, or derives from any of the types in
+  /// `TUPLE`.
+  template <typename TUPLE>
+  inline bool IsAnyOfTuple() const {
+    constexpr auto kCount = std::tuple_size_v<TUPLE>;
+    if constexpr (kCount == 0) {
+      return false;
+    } else if constexpr (kCount == 1) {
+      return Is(&Of<std::tuple_element_t<0, TUPLE>>());
+    } else if constexpr (kCount == 2) {
+      return Is(&Of<std::tuple_element_t<0, TUPLE>>()) ||
+             Is(&Of<std::tuple_element_t<1, TUPLE>>());
+    } else if constexpr (kCount == 3) {
+      return Is(&Of<std::tuple_element_t<0, TUPLE>>()) ||
+             Is(&Of<std::tuple_element_t<1, TUPLE>>()) ||
+             Is(&Of<std::tuple_element_t<2, TUPLE>>());
+    } else {
+      // Optimization: Compare the object's hashcode to the bitwise-or of all
+      // the tested type's hashcodes. If there's no intersection of bits in
+      // the two masks, then we can guarantee that the type is not in `TO`.
+      if (full_hashcode & TypeInfo::CombinedHashCodeOfTuple<TUPLE>()) {
+        // Possibly one of the types in `TUPLE`.
+        // Split the search in two, and scan each block.
+        static constexpr auto kMid = kCount / 2;
+        return IsAnyOfTuple<traits::SliceTuple<0, kMid, TUPLE>>() ||
+               IsAnyOfTuple<traits::SliceTuple<kMid, kCount - kMid, TUPLE>>();
+      }
+      return false;
+    }
+  }
+
+  /// @returns true if this TypeInfo is of, or derives from any of the types in
+  /// `TYPES`.
+  template <typename... TYPES>
+  inline bool IsAnyOf() const {
+    return IsAnyOfTuple<std::tuple<TYPES...>>();
+  }
 };
 
 namespace detail {
@@ -181,10 +246,6 @@
   static const TypeInfo info;
 };
 
-// Forward declaration
-template <typename TO_FIRST, typename... TO_REST>
-struct IsAnyOf;
-
 /// A placeholder structure used for template parameters that need a default
 /// type, but can always be automatically inferred.
 struct Infer;
@@ -204,39 +265,29 @@
 }
 
 /// @returns true if `obj` is a valid pointer, and is of, or derives from the
-/// class `TO`, and pred(const TO*) returns true
+/// type `TYPE`, and pred(const TYPE*) returns true
 /// @param obj the object to test from
-/// @param pred predicate function with signature `bool(const TO*)` called iff
-/// object is of, or derives from the class `TO`.
+/// @param pred predicate function with signature `bool(const TYPE*)` called iff
+/// object is of, or derives from the class `TYPE`.
 /// @see CastFlags
-template <typename TO,
+template <typename TYPE,
           int FLAGS = 0,
-          typename FROM = detail::Infer,
+          typename OBJ = detail::Infer,
           typename Pred = detail::Infer>
-inline bool Is(FROM* obj, Pred&& pred) {
-  return Is<TO, FLAGS, FROM>(obj) &&
-         pred(static_cast<std::add_const_t<TO>*>(obj));
+inline bool Is(OBJ* obj, Pred&& pred) {
+  return Is<TYPE, FLAGS, OBJ>(obj) &&
+         pred(static_cast<std::add_const_t<TYPE>*>(obj));
 }
 
-/// @returns true if `obj` is of, or derives from any of the `TO`
-/// classes.
-/// @param obj the object to cast from
-template <typename... TO, typename FROM>
-inline bool IsAnyOf(FROM* obj) {
+/// @returns true if `obj` is a valid pointer, and is of, or derives from any of
+/// the types in `TYPES`.OBJ
+/// @param obj the object to query.
+template <typename... TYPES, typename OBJ>
+inline bool IsAnyOf(OBJ* obj) {
   if (!obj) {
     return false;
   }
-  // Optimization: Compare the object's combined_hashcode to the bitwise-or of
-  // all the tested type's hashcodes. If there's no intersection of bits in the
-  // two masks, then we can guarantee that the type is not in `TO`.
-  using Helper = detail::IsAnyOf<TO...>;
-  auto* type = &obj->TypeInfo();
-  auto hashcode = type->combined_hashcode;
-  if ((Helper::kHashCodes & hashcode) == 0) {
-    return false;
-  }
-  // Possibly one of the types in `TO`. Continue to testing against each type.
-  return Helper::template Exec<FROM>(type);
+  return obj->TypeInfo().template IsAnyOf<TYPES...>();
 }
 
 /// @returns obj dynamically cast to the type `TO` or `nullptr` if
@@ -402,38 +453,6 @@
   }
 };
 
-namespace detail {
-/// Helper for Castable::IsAnyOf
-template <typename TO_FIRST, typename... TO_REST>
-struct IsAnyOf {
-  /// The bitwise-or of all typeinfo hashcodes
-  static constexpr auto kHashCodes =
-      TypeInfo::HashCodeOf<TO_FIRST>() | IsAnyOf<TO_REST...>::kHashCodes;
-
-  /// @param type castable object type to test
-  /// @returns true if `obj` is of, or derives from any of `[TO_FIRST,
-  /// ...TO_REST]`
-  template <typename FROM>
-  static bool Exec(const TypeInfo* type) {
-    return TypeInfo::Is<TO_FIRST, FROM>(type) ||
-           IsAnyOf<TO_REST...>::template Exec<FROM>(type);
-  }
-};
-/// Terminal specialization
-template <typename TO>
-struct IsAnyOf<TO> {
-  /// The bitwise-or of all typeinfo hashcodes
-  static constexpr auto kHashCodes = TypeInfo::HashCodeOf<TO>();
-
-  /// @param type castable object type to test
-  /// @returns true if `obj` is of, or derives from TO
-  template <typename FROM>
-  static bool Exec(const TypeInfo* type) {
-    return TypeInfo::Is<TO, FROM>(type);
-  }
-};
-}  // namespace detail
-
 }  // namespace tint
 
 TINT_CASTABLE_POP_DISABLE_WARNINGS();
diff --git a/src/traits.h b/src/traits.h
index 5b933b9..4d2cbab 100644
--- a/src/traits.h
+++ b/src/traits.h
@@ -16,9 +16,9 @@
 #define SRC_TRAITS_H_
 
 #include <tuple>
+#include <utility>
 
-namespace tint {
-namespace traits {
+namespace tint::traits {
 
 /// Convience type definition for std::decay<T>::type
 template <typename T>
@@ -109,7 +109,51 @@
 template <typename T, typename BASE>
 using EnableIfIsNotType = EnableIf<!IsTypeOrDerived<T, BASE>::value, T>;
 
-}  // namespace traits
-}  // namespace tint
+/// @returns the std::index_sequence with all the indices shifted by OFFSET.
+template <std::size_t OFFSET, std::size_t... INDICES>
+constexpr auto Shift(std::index_sequence<INDICES...>) {
+  return std::integer_sequence<std::size_t, OFFSET + INDICES...>{};
+}
+
+/// @returns a std::integer_sequence with the integers `[OFFSET..OFFSET+COUNT)`
+template <std::size_t OFFSET, std::size_t COUNT>
+constexpr auto Range() {
+  return Shift<OFFSET>(std::make_index_sequence<COUNT>{});
+}
+
+namespace detail {
+
+/// @returns the tuple `t` swizzled by `INDICES`
+template <class TUPLE, std::size_t... INDICES>
+constexpr auto Swizzle(TUPLE&& t, std::index_sequence<INDICES...>) {
+  return std::make_tuple(std::get<INDICES>(std::forward<TUPLE>(t))...);
+}
+
+/// @returns a nullptr of the tuple type `TUPLE` swizzled by `INDICES`.
+/// @note: This function is intended to be used in a `decltype()` expression,
+/// and returns a pointer-to-tuple as the tuple may hold non-constructable
+/// types.
+template <class TUPLE, std::size_t... INDICES>
+constexpr auto* SwizzlePtrTy(std::index_sequence<INDICES...>) {
+  using Swizzled = std::tuple<std::tuple_element_t<INDICES, TUPLE>...>;
+  return static_cast<Swizzled*>(nullptr);
+}
+
+}  // namespace detail
+
+/// @returns the slice of the tuple `t` with the tuple elements
+/// `[OFFSET..OFFSET+COUNT)`
+template <std::size_t OFFSET, std::size_t COUNT, typename TUPLE>
+constexpr auto Slice(TUPLE&& t) {
+  return detail::Swizzle<TUPLE>(std::forward<TUPLE>(t), Range<OFFSET, COUNT>());
+}
+
+/// Resolves to the slice of the tuple `t` with the tuple elements
+/// `[OFFSET..OFFSET+COUNT)`
+template <std::size_t OFFSET, std::size_t COUNT, typename TUPLE>
+using SliceTuple = std::remove_pointer_t<decltype(
+    detail::SwizzlePtrTy<TUPLE>(Range<OFFSET, COUNT>()))>;
+
+}  // namespace tint::traits
 
 #endif  // SRC_TRAITS_H_
diff --git a/src/traits_test.cc b/src/traits_test.cc
index 43cd209..7453866 100644
--- a/src/traits_test.cc
+++ b/src/traits_test.cc
@@ -28,13 +28,12 @@
 TEST(ParamType, Function) {
   F1({});        // Avoid unused method warning
   F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same<ParameterType<decltype(&F1), 0>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(&F3), 0>, int>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(&F3), 1>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(&F3), 2>, float>::value,
-                "");
-  static_assert(std::is_same<ReturnType<decltype(&F1)>, void>::value, "");
-  static_assert(std::is_same<ReturnType<decltype(&F3)>, void>::value, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&F1), 0>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&F3), 0>, int>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&F3), 1>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&F3), 2>, float>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(&F1)>, void>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(&F3)>, void>, "");
   static_assert(SignatureOfT<decltype(&F1)>::parameter_count == 1, "");
   static_assert(SignatureOfT<decltype(&F3)>::parameter_count == 3, "");
 }
@@ -47,14 +46,12 @@
   };
   C().F1({});        // Avoid unused method warning
   C().F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same<ParameterType<decltype(&C::F1), 0>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(&C::F3), 0>, int>::value,
-                "");
-  static_assert(std::is_same<ParameterType<decltype(&C::F3), 1>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(&C::F3), 2>, float>::value,
-                "");
-  static_assert(std::is_same<ReturnType<decltype(&C::F1)>, void>::value, "");
-  static_assert(std::is_same<ReturnType<decltype(&C::F3)>, void>::value, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>, "");
   static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1, "");
   static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3, "");
 }
@@ -67,14 +64,12 @@
   };
   C().F1({});        // Avoid unused method warning
   C().F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same<ParameterType<decltype(&C::F1), 0>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(&C::F3), 0>, int>::value,
-                "");
-  static_assert(std::is_same<ParameterType<decltype(&C::F3), 1>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(&C::F3), 2>, float>::value,
-                "");
-  static_assert(std::is_same<ReturnType<decltype(&C::F1)>, void>::value, "");
-  static_assert(std::is_same<ReturnType<decltype(&C::F3)>, void>::value, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>, "");
   static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1, "");
   static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3, "");
 }
@@ -87,14 +82,12 @@
   };
   C::F1({});        // Avoid unused method warning
   C::F3(0, {}, 0);  // Avoid unused method warning
-  static_assert(std::is_same<ParameterType<decltype(&C::F1), 0>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(&C::F3), 0>, int>::value,
-                "");
-  static_assert(std::is_same<ParameterType<decltype(&C::F3), 1>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(&C::F3), 2>, float>::value,
-                "");
-  static_assert(std::is_same<ReturnType<decltype(&C::F1)>, void>::value, "");
-  static_assert(std::is_same<ReturnType<decltype(&C::F3)>, void>::value, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>, "");
   static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1, "");
   static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3, "");
 }
@@ -102,12 +95,12 @@
 TEST(ParamType, FunctionLike) {
   using F1 = std::function<void(S)>;
   using F3 = std::function<void(int, S, float)>;
-  static_assert(std::is_same<ParameterType<F1, 0>, S>::value, "");
-  static_assert(std::is_same<ParameterType<F3, 0>, int>::value, "");
-  static_assert(std::is_same<ParameterType<F3, 1>, S>::value, "");
-  static_assert(std::is_same<ParameterType<F3, 2>, float>::value, "");
-  static_assert(std::is_same<ReturnType<F1>, void>::value, "");
-  static_assert(std::is_same<ReturnType<F3>, void>::value, "");
+  static_assert(std::is_same_v<ParameterType<F1, 0>, S>, "");
+  static_assert(std::is_same_v<ParameterType<F3, 0>, int>, "");
+  static_assert(std::is_same_v<ParameterType<F3, 1>, S>, "");
+  static_assert(std::is_same_v<ParameterType<F3, 2>, float>, "");
+  static_assert(std::is_same_v<ReturnType<F1>, void>, "");
+  static_assert(std::is_same_v<ReturnType<F3>, void>, "");
   static_assert(SignatureOfT<F1>::parameter_count == 1, "");
   static_assert(SignatureOfT<F3>::parameter_count == 3, "");
 }
@@ -115,15 +108,117 @@
 TEST(ParamType, Lambda) {
   auto l1 = [](S) {};
   auto l3 = [](int, S, float) {};
-  static_assert(std::is_same<ParameterType<decltype(l1), 0>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(l3), 0>, int>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(l3), 1>, S>::value, "");
-  static_assert(std::is_same<ParameterType<decltype(l3), 2>, float>::value, "");
-  static_assert(std::is_same<ReturnType<decltype(l1)>, void>::value, "");
-  static_assert(std::is_same<ReturnType<decltype(l3)>, void>::value, "");
+  static_assert(std::is_same_v<ParameterType<decltype(l1), 0>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(l3), 0>, int>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(l3), 1>, S>, "");
+  static_assert(std::is_same_v<ParameterType<decltype(l3), 2>, float>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(l1)>, void>, "");
+  static_assert(std::is_same_v<ReturnType<decltype(l3)>, void>, "");
   static_assert(SignatureOfT<decltype(l1)>::parameter_count == 1, "");
   static_assert(SignatureOfT<decltype(l3)>::parameter_count == 3, "");
 }
 
+TEST(Slice, Empty) {
+  auto sliced = Slice<0, 0>(std::make_tuple<>());
+  static_assert(std::tuple_size_v<decltype(sliced)> == 0, "");
+}
+
+TEST(Slice, SingleElementSliceEmpty) {
+  auto sliced = Slice<0, 0>(std::make_tuple<int>(1));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 0, "");
+}
+
+TEST(Slice, SingleElementSliceFull) {
+  auto sliced = Slice<0, 1>(std::make_tuple<int>(1));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 1, "");
+  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>,
+                "");
+  EXPECT_EQ(std::get<0>(sliced), 1);
+}
+
+TEST(Slice, MixedTupleSliceEmpty) {
+  auto sliced = Slice<1, 0>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 0, "");
+}
+
+TEST(Slice, MixedTupleSliceFull) {
+  auto sliced = Slice<0, 3>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 3, "");
+  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>,
+                "");
+  static_assert(std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, bool>,
+                "");
+  static_assert(
+      std::is_same_v<std::tuple_element_t<2, decltype(sliced)>, float>, "");
+  EXPECT_EQ(std::get<0>(sliced), 1);
+  EXPECT_EQ(std::get<1>(sliced), true);
+  EXPECT_EQ(std::get<2>(sliced), 2.0f);
+}
+
+TEST(Slice, MixedTupleSliceLowPart) {
+  auto sliced = Slice<0, 2>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 2, "");
+  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>,
+                "");
+  static_assert(std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, bool>,
+                "");
+  EXPECT_EQ(std::get<0>(sliced), 1);
+  EXPECT_EQ(std::get<1>(sliced), true);
+}
+
+TEST(Slice, MixedTupleSliceHighPart) {
+  auto sliced = Slice<1, 2>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+  static_assert(std::tuple_size_v<decltype(sliced)> == 2, "");
+  static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, bool>,
+                "");
+  static_assert(
+      std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, float>, "");
+  EXPECT_EQ(std::get<0>(sliced), true);
+  EXPECT_EQ(std::get<1>(sliced), 2.0f);
+}
+
+TEST(SliceTuple, Empty) {
+  using sliced = SliceTuple<0, 0, std::tuple<>>;
+  static_assert(std::tuple_size_v<sliced> == 0, "");
+}
+
+TEST(SliceTuple, SingleElementSliceEmpty) {
+  using sliced = SliceTuple<0, 0, std::tuple<int>>;
+  static_assert(std::tuple_size_v<sliced> == 0, "");
+}
+
+TEST(SliceTuple, SingleElementSliceFull) {
+  using sliced = SliceTuple<0, 1, std::tuple<int>>;
+  static_assert(std::tuple_size_v<sliced> == 1, "");
+  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>, "");
+}
+
+TEST(SliceTuple, MixedTupleSliceEmpty) {
+  using sliced = SliceTuple<1, 0, std::tuple<int, bool, float>>;
+  static_assert(std::tuple_size_v<sliced> == 0, "");
+}
+
+TEST(SliceTuple, MixedTupleSliceFull) {
+  using sliced = SliceTuple<0, 3, std::tuple<int, bool, float>>;
+  static_assert(std::tuple_size_v<sliced> == 3, "");
+  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>, "");
+  static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, bool>, "");
+  static_assert(std::is_same_v<std::tuple_element_t<2, sliced>, float>, "");
+}
+
+TEST(SliceTuple, MixedTupleSliceLowPart) {
+  using sliced = SliceTuple<0, 2, std::tuple<int, bool, float>>;
+  static_assert(std::tuple_size_v<sliced> == 2, "");
+  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>, "");
+  static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, bool>, "");
+}
+
+TEST(SliceTuple, MixedTupleSliceHighPart) {
+  using sliced = SliceTuple<1, 2, std::tuple<int, bool, float>>;
+  static_assert(std::tuple_size_v<sliced> == 2, "");
+  static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, bool>, "");
+  static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, float>, "");
+}
+
 }  // namespace traits
 }  // namespace tint