diff --git a/common/src/KokkosFFT_traits.hpp b/common/src/KokkosFFT_traits.hpp
index ec89df49..312797bd 100644
--- a/common/src/KokkosFFT_traits.hpp
+++ b/common/src/KokkosFFT_traits.hpp
@@ -9,6 +9,9 @@
 
 namespace KokkosFFT {
 namespace Impl {
+
+// Traits for unary operation
+
 template <typename T>
 struct base_floating_point {
   using value_type = T;
@@ -60,15 +63,15 @@ struct is_admissible_value_type<
 
 template <typename T>
 struct is_admissible_value_type<
-    T, std::enable_if_t<Kokkos::is_view<T>::value &&
+    T, std::enable_if_t<Kokkos::is_view_v<T> &&
                         (is_real_v<typename T::non_const_value_type> ||
                          is_complex_v<typename T::non_const_value_type>)>>
     : std::true_type {};
 
 /// \brief Helper to check if a type is an acceptable value type
 /// (float/double/Kokkos::complex<float>/Kokkos::complex<double>) for Kokkos-FFT
-///        When applied to Kokkos::View, then check if a value type is an
-///        acceptable real/complex type.
+/// When applied to Kokkos::View, then check if a value type is an acceptable
+/// real/complex type.
 template <typename T>
 inline constexpr bool is_admissible_value_type_v =
     is_admissible_value_type<T>::value;
@@ -80,7 +83,7 @@ template <typename ViewType>
 struct is_layout_left_or_right<
     ViewType,
     std::enable_if_t<
-        Kokkos::is_view<ViewType>::value &&
+        Kokkos::is_view_v<ViewType> &&
         (std::is_same_v<typename ViewType::array_layout, Kokkos::LayoutLeft> ||
          std::is_same_v<typename ViewType::array_layout, Kokkos::LayoutRight>)>>
     : std::true_type {};
@@ -96,7 +99,7 @@ struct is_admissible_view : std::false_type {};
 
 template <typename ViewType>
 struct is_admissible_view<
-    ViewType, std::enable_if_t<Kokkos::is_view<ViewType>::value &&
+    ViewType, std::enable_if_t<Kokkos::is_view_v<ViewType> &&
                                is_layout_left_or_right_v<ViewType> &&
                                is_admissible_value_type_v<ViewType>>>
     : std::true_type {};
@@ -107,6 +110,111 @@ template <typename ViewType>
 inline constexpr bool is_admissible_view_v =
     is_admissible_view<ViewType>::value;
 
+template <typename ExecutionSpace, typename ViewType, typename Enable = void>
+struct is_operatable_view : std::false_type {};
+
+template <typename ExecutionSpace, typename ViewType>
+struct is_operatable_view<
+    ExecutionSpace, ViewType,
+    std::enable_if_t<
+        Kokkos::is_execution_space_v<ExecutionSpace> &&
+        is_admissible_view_v<ViewType> &&
+        Kokkos::SpaceAccessibility<
+            ExecutionSpace, typename ViewType::memory_space>::accessible>>
+    : std::true_type {};
+
+/// \brief Helper to check if a View is an acceptable View for Kokkos-FFT and
+/// memory space is accessible from the ExecutionSpace
+template <typename ExecutionSpace, typename ViewType>
+inline constexpr bool is_operatable_view_v =
+    is_operatable_view<ExecutionSpace, ViewType>::value;
+
+// Traits for binary operations
+template <typename T1, typename T2, typename Enable = void>
+struct have_same_base_floating_point_type : std::false_type {};
+
+template <typename T1, typename T2>
+struct have_same_base_floating_point_type<
+    T1, T2,
+    std::enable_if_t<!Kokkos::is_view_v<T1> && !Kokkos::is_view_v<T2> &&
+                     std::is_same_v<base_floating_point_type<T1>,
+                                    base_floating_point_type<T2>>>>
+    : std::true_type {};
+
+template <typename InViewType, typename OutViewType>
+struct have_same_base_floating_point_type<
+    InViewType, OutViewType,
+    std::enable_if_t<
+        Kokkos::is_view_v<InViewType> && Kokkos::is_view_v<OutViewType> &&
+        std::is_same_v<
+            base_floating_point_type<typename InViewType::non_const_value_type>,
+            base_floating_point_type<
+                typename OutViewType::non_const_value_type>>>>
+    : std::true_type {};
+
+/// \brief Helper to check if two value have the same base floating point type.
+/// When applied to Kokkos::View, then check if values of views have the same
+/// base floating point type.
+template <typename T1, typename T2>
+inline constexpr bool have_same_base_floating_point_type_v =
+    have_same_base_floating_point_type<T1, T2>::value;
+
+template <typename InViewType, typename OutViewType, typename Enable = void>
+struct have_same_layout : std::false_type {};
+
+template <typename InViewType, typename OutViewType>
+struct have_same_layout<
+    InViewType, OutViewType,
+    std::enable_if_t<Kokkos::is_view_v<InViewType> &&
+                     Kokkos::is_view_v<OutViewType> &&
+                     std::is_same_v<typename InViewType::array_layout,
+                                    typename OutViewType::array_layout>>>
+    : std::true_type {};
+
+/// \brief Helper to check if two views have the same layout type.
+template <typename InViewType, typename OutViewType>
+inline constexpr bool have_same_layout_v =
+    have_same_layout<InViewType, OutViewType>::value;
+
+template <typename InViewType, typename OutViewType, typename Enable = void>
+struct have_same_rank : std::false_type {};
+
+template <typename InViewType, typename OutViewType>
+struct have_same_rank<
+    InViewType, OutViewType,
+    std::enable_if_t<Kokkos::is_view_v<InViewType> &&
+                     Kokkos::is_view_v<OutViewType> &&
+                     InViewType::rank() == OutViewType::rank()>>
+    : std::true_type {};
+
+/// \brief Helper to check if two views have the same rank.
+template <typename InViewType, typename OutViewType>
+inline constexpr bool have_same_rank_v =
+    have_same_rank<InViewType, OutViewType>::value;
+
+template <typename ExecutionSpace, typename InViewType, typename OutViewType,
+          typename Enable = void>
+struct are_operatable_views : std::false_type {};
+
+template <typename ExecutionSpace, typename InViewType, typename OutViewType>
+struct are_operatable_views<
+    ExecutionSpace, InViewType, OutViewType,
+    std::enable_if_t<
+        is_operatable_view_v<ExecutionSpace, InViewType> &&
+        is_operatable_view_v<ExecutionSpace, OutViewType> &&
+        have_same_base_floating_point_type_v<InViewType, OutViewType> &&
+        have_same_layout_v<InViewType, OutViewType> &&
+        have_same_rank_v<InViewType, OutViewType>>> : std::true_type {};
+
+/// \brief Helper to check if Views are acceptable View for Kokkos-FFT and
+/// memory space are accessible from the ExecutionSpace.
+/// In addition, precisions, layout and rank are checked to be identical.
+template <typename ExecutionSpace, typename InViewType, typename OutViewType>
+inline constexpr bool are_operatable_views_v =
+    are_operatable_views<ExecutionSpace, InViewType, OutViewType>::value;
+
+// Other traits
+
 /// \brief Helper to define a managable View type from the original view type
 template <typename T>
 struct managable_view_type {
diff --git a/common/unit_test/Test_Traits.cpp b/common/unit_test/Test_Traits.cpp
index 01eb2e02..8d09753d 100644
--- a/common/unit_test/Test_Traits.cpp
+++ b/common/unit_test/Test_Traits.cpp
@@ -6,6 +6,16 @@
 #include "KokkosFFT_traits.hpp"
 #include "Test_Utils.hpp"
 
+// All the tests in this file are compile time tests, so we skip all the tests
+// by GTEST_SKIP(). gtest is used for type parameterization.
+
+// Define the types to combine
+using base_real_types = std::tuple<float, double, long double>;
+
+// Define the layouts to combine
+using base_layout_types =
+    std::tuple<Kokkos::LayoutLeft, Kokkos::LayoutRight, Kokkos::LayoutStride>;
+
 using real_types = ::testing::Types<float, double, long double>;
 using view_types =
     ::testing::Types<std::pair<float, Kokkos::LayoutLeft>,
@@ -18,10 +28,25 @@ using view_types =
                      std::pair<long double, Kokkos::LayoutRight>,
                      std::pair<long double, Kokkos::LayoutStride>>;
 
+// Define all the combinations
+using paired_value_types =
+    tuple_to_types_t<cartesian_product_t<base_real_types, base_real_types>>;
+
+using paired_layout_types =
+    tuple_to_types_t<cartesian_product_t<base_layout_types, base_layout_types>>;
+
+using paired_view_types =
+    tuple_to_types_t<cartesian_product_t<base_real_types, base_layout_types,
+                                         base_real_types, base_layout_types>>;
+
 template <typename T>
 struct RealAndComplexTypes : public ::testing::Test {
   using real_type    = T;
   using complex_type = Kokkos::complex<T>;
+
+  virtual void SetUp() {
+    GTEST_SKIP() << "Skipping all tests for this fixture";
+  }
 };
 
 template <typename T>
@@ -29,10 +54,48 @@ struct RealAndComplexViewTypes : public ::testing::Test {
   using real_type    = typename T::first_type;
   using complex_type = Kokkos::complex<real_type>;
   using layout_type  = typename T::second_type;
+  virtual void SetUp() {
+    GTEST_SKIP() << "Skipping all tests for this fixture";
+  }
+};
+
+template <typename T>
+struct PairedValueTypes : public ::testing::Test {
+  using real_type1 = typename std::tuple_element_t<0, T>;
+  using real_type2 = typename std::tuple_element_t<1, T>;
+
+  virtual void SetUp() {
+    GTEST_SKIP() << "Skipping all tests for this fixture";
+  }
+};
+
+template <typename T>
+struct PairedLayoutTypes : public ::testing::Test {
+  using layout_type1 = typename std::tuple_element_t<0, T>;
+  using layout_type2 = typename std::tuple_element_t<1, T>;
+
+  virtual void SetUp() {
+    GTEST_SKIP() << "Skipping all tests for this fixture";
+  }
+};
+
+template <typename T>
+struct PairedViewTypes : public ::testing::Test {
+  using real_type1   = typename std::tuple_element_t<0, T>;
+  using layout_type1 = typename std::tuple_element_t<1, T>;
+  using real_type2   = typename std::tuple_element_t<2, T>;
+  using layout_type2 = typename std::tuple_element_t<3, T>;
+
+  virtual void SetUp() {
+    GTEST_SKIP() << "Skipping all tests for this fixture";
+  }
 };
 
 TYPED_TEST_SUITE(RealAndComplexTypes, real_types);
 TYPED_TEST_SUITE(RealAndComplexViewTypes, view_types);
+TYPED_TEST_SUITE(PairedValueTypes, paired_value_types);
+TYPED_TEST_SUITE(PairedLayoutTypes, paired_layout_types);
+TYPED_TEST_SUITE(PairedViewTypes, paired_view_types);
 
 // Tests for real type deduction
 template <typename RealType, typename ComplexType>
@@ -42,8 +105,11 @@ void test_get_real_type() {
   using real_type_from_ComplexType =
       KokkosFFT::Impl::base_floating_point_type<ComplexType>;
 
+  // base floating point type of RealType is RealType
   static_assert(std::is_same_v<real_type_from_RealType, RealType>,
                 "Real type not deduced correctly from real type");
+
+  // base floating point type of Kokkos::complex<RealType> is RealType
   static_assert(std::is_same_v<real_type_from_ComplexType, RealType>,
                 "Real type not deduced correctly from complex type");
 }
@@ -51,26 +117,32 @@ void test_get_real_type() {
 // Tests for admissible real types (float or double)
 template <typename T>
 void test_admissible_real_type() {
+  // Tests that a real type is float or double
   if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
+    // T is float or double
     static_assert(KokkosFFT::Impl::is_real_v<T>,
                   "Real type must be float or double");
   } else {
+    // T is not float or double
     static_assert(!KokkosFFT::Impl::is_real_v<T>,
-                  "Real type must be float or double");
+                  "Real type must not be float or double");
   }
 }
 
 template <typename T>
 void test_admissible_complex_type() {
   using real_type = KokkosFFT::Impl::base_floating_point_type<T>;
+  // Tests that a base floating point type of complex value is float or double
   if constexpr (std::is_same_v<real_type, float> ||
                 std::is_same_v<real_type, double>) {
+    // T is Kokkos::complex<float> or Kokkos::complex<double>
     static_assert(KokkosFFT::Impl::is_complex_v<T>,
                   "Complex type must be Kokkos::complex<float> or "
                   "Kokkos::complex<double>");
   } else {
+    // T is not Kokkos::complex<float> or Kokkos::complex<double>
     static_assert(!KokkosFFT::Impl::is_complex_v<T>,
-                  "Complex type must be Kokkos::complex<float> or "
+                  "Complex type must not be Kokkos::complex<float> or "
                   "Kokkos::complex<double>");
   }
 }
@@ -99,26 +171,38 @@ template <typename T, typename LayoutType>
 void test_admissible_value_type() {
   using ViewType  = Kokkos::View<T*, LayoutType>;
   using real_type = KokkosFFT::Impl::base_floating_point_type<T>;
+  // Tests that a Value or View has a addmissible value type
   if constexpr (std::is_same_v<real_type, float> ||
                 std::is_same_v<real_type, double>) {
+    // Base floating point type of a Value is float or double
+    static_assert(KokkosFFT::Impl::is_admissible_value_type_v<T>,
+                  "Base value type must be float or double");
+
+    // Base floating point type of a View is float or double
     static_assert(KokkosFFT::Impl::is_admissible_value_type_v<ViewType>,
-                  "Real type must be float or double");
+                  "Base value type of a View must be float or double");
   } else {
+    // Base floating point type of a Value is not float or double
+    static_assert(!KokkosFFT::Impl::is_admissible_value_type_v<T>,
+                  "Base value type of a View must not be float or double");
+
+    // Base floating point type of a View is not float or double
     static_assert(!KokkosFFT::Impl::is_admissible_value_type_v<ViewType>,
-                  "Real type must be float or double");
+                  "Base value type of a View must not be float or double");
   }
 }
 
 template <typename T, typename LayoutType>
 void test_admissible_layout_type() {
   using ViewType = Kokkos::View<T*, LayoutType>;
+  // Tests that the View has a layout in LayoutLeft or LayoutRight
   if constexpr (std::is_same_v<LayoutType, Kokkos::LayoutLeft> ||
                 std::is_same_v<LayoutType, Kokkos::LayoutRight>) {
     static_assert(KokkosFFT::Impl::is_layout_left_or_right_v<ViewType>,
                   "View Layout must be either LayoutLeft or LayoutRight.");
   } else {
     static_assert(!KokkosFFT::Impl::is_layout_left_or_right_v<ViewType>,
-                  "View Layout must be either LayoutLeft or LayoutRight.");
+                  "View Layout must not be either LayoutLeft or LayoutRight.");
   }
 }
 
@@ -126,19 +210,78 @@ template <typename T, typename LayoutType>
 void test_admissible_view_type() {
   using ViewType  = Kokkos::View<T*, LayoutType>;
   using real_type = KokkosFFT::Impl::base_floating_point_type<T>;
-  if constexpr (
-      (std::is_same_v<real_type, float> || std::is_same_v<real_type, double>)&&(
-          std::is_same_v<LayoutType, Kokkos::LayoutLeft> ||
-          std::is_same_v<LayoutType, Kokkos::LayoutRight>)) {
-    static_assert(KokkosFFT::Impl::is_admissible_view_v<ViewType>,
-                  "View value type must be float, double, "
-                  "Kokkos::Complex<float>, Kokkos::Complex<double>. Layout "
-                  "must be either LayoutLeft or LayoutRight.");
+
+  // Tests that the View has a base floating point type in float or double
+  if constexpr ((std::is_same_v<real_type, float> ||
+                 std::is_same_v<real_type, double>)) {
+    // Tests that the View has a layout in LayoutLeft or LayoutRight
+    if constexpr (std::is_same_v<LayoutType, Kokkos::LayoutLeft> ||
+                  std::is_same_v<LayoutType, Kokkos::LayoutRight>) {
+      static_assert(KokkosFFT::Impl::is_admissible_view_v<ViewType>,
+                    "View value type must be float, double, "
+                    "Kokkos::Complex<float>, Kokkos::Complex<double>. Layout "
+                    "must be either LayoutLeft or LayoutRight.");
+    } else {
+      // View is not admissible because layout is not in LayoutLeft or
+      // LayoutRight
+      static_assert(!KokkosFFT::Impl::is_admissible_view_v<ViewType>,
+                    "Layout must be either LayoutLeft or LayoutRight.");
+    }
   } else {
+    // View is not admissible because the base floating point type is not in
+    // float or double
     static_assert(!KokkosFFT::Impl::is_admissible_view_v<ViewType>,
-                  "View value type must be float, double, "
-                  "Kokkos::Complex<float>, Kokkos::Complex<double>. Layout "
-                  "must be either LayoutLeft or LayoutRight.");
+                  "Base value type must be float or double");
+  }
+}
+
+// \brief Test if a View is operatable
+// \tparam ExecutionSpace1 Execution space for the device
+// \tparam ExecutionSpace2 Execution space for the View memory space
+// \tparam T Value type of the View
+// \tparam LayoutType Layout type of the View
+template <typename ExecutionSpace1, typename ExecutionSpace2, typename T,
+          typename LayoutType>
+void test_operatable_view_type() {
+  using ViewType  = Kokkos::View<T*, LayoutType, ExecutionSpace2>;
+  using real_type = KokkosFFT::Impl::base_floating_point_type<T>;
+
+  // Tests that a View is accessible from the ExecutionSpace
+  if constexpr (Kokkos::SpaceAccessibility<
+                    ExecutionSpace1,
+                    typename ViewType::memory_space>::accessible) {
+    // Tests that the View has a base floating point type in float or double
+    if constexpr ((std::is_same_v<real_type, float> ||
+                   std::is_same_v<real_type, double>)) {
+      // Tests that the View has a layout in LayoutLeft or LayoutRight
+      if constexpr (std::is_same_v<LayoutType, Kokkos::LayoutLeft> ||
+                    std::is_same_v<LayoutType, Kokkos::LayoutRight>) {
+        // View is operatable
+        static_assert(
+            KokkosFFT::Impl::is_operatable_view_v<ExecutionSpace1, ViewType>,
+            "View value type must be float, double, "
+            "Kokkos::Complex<float>, or Kokkos::Complex<double>. Layout "
+            "must be either LayoutLeft or LayoutRight.");
+      } else {
+        // View is not operatable because layout is not in LayoutLeft or
+        // LayoutRight
+        static_assert(
+            !KokkosFFT::Impl::is_operatable_view_v<ExecutionSpace1, ViewType>,
+            "Layout must be either LayoutLeft or LayoutRight.");
+      }
+    } else {
+      // View is not operatable because the base floating point type is not in
+      // float or double
+      static_assert(
+          !KokkosFFT::Impl::is_operatable_view_v<ExecutionSpace1, ViewType>,
+          "Base value type must be float or double");
+    }
+  } else {
+    // View is not operatable because it is not accessible from
+    // ExecutionSpace
+    static_assert(
+        !KokkosFFT::Impl::is_operatable_view_v<ExecutionSpace1, ViewType>,
+        "ExecutionSpace cannot access data in ViewType");
   }
 }
 
@@ -168,3 +311,330 @@ TYPED_TEST(RealAndComplexViewTypes, admissible_view_type) {
   test_admissible_view_type<real_type, layout_type>();
   test_admissible_view_type<complex_type, layout_type>();
 }
+
+TYPED_TEST(RealAndComplexViewTypes, operatable_view_type) {
+  using real_type    = typename TestFixture::real_type;
+  using complex_type = typename TestFixture::complex_type;
+  using layout_type  = typename TestFixture::layout_type;
+  using host_space   = Kokkos::DefaultHostExecutionSpace;
+  using device_space = Kokkos::DefaultExecutionSpace;
+
+  test_operatable_view_type<host_space, host_space, real_type, layout_type>();
+  test_operatable_view_type<host_space, device_space, real_type, layout_type>();
+  test_operatable_view_type<device_space, host_space, real_type, layout_type>();
+  test_operatable_view_type<device_space, device_space, real_type,
+                            layout_type>();
+
+  test_operatable_view_type<host_space, host_space, complex_type,
+                            layout_type>();
+  test_operatable_view_type<host_space, device_space, complex_type,
+                            layout_type>();
+  test_operatable_view_type<device_space, host_space, complex_type,
+                            layout_type>();
+  test_operatable_view_type<device_space, device_space, complex_type,
+                            layout_type>();
+}
+
+// Tests for multiple Views
+template <typename RealType1, typename RealType2>
+void test_have_same_base_floating_point_type() {
+  using real_type1    = RealType1;
+  using real_type2    = RealType2;
+  using complex_type1 = Kokkos::complex<real_type1>;
+  using complex_type2 = Kokkos::complex<real_type2>;
+
+  using RealViewType1    = Kokkos::View<real_type1*>;
+  using ComplexViewType1 = Kokkos::View<complex_type1*>;
+  using RealViewType2    = Kokkos::View<real_type2*>;
+  using ComplexViewType2 = Kokkos::View<complex_type2*>;
+
+  // Tests that Values or Views have the same base floating point type
+  if constexpr (std::is_same_v<real_type1, real_type2>) {
+    // Values must have the same base floating point type
+    static_assert(
+        KokkosFFT::Impl::have_same_base_floating_point_type_v<real_type1,
+                                                              complex_type1>,
+        "Values must have the same base floating point type");
+    static_assert(
+        KokkosFFT::Impl::have_same_base_floating_point_type_v<complex_type1,
+                                                              real_type2>,
+        "Values must have the same base floating point type");
+    static_assert(
+        KokkosFFT::Impl::have_same_base_floating_point_type_v<complex_type1,
+                                                              complex_type2>,
+        "Values must have the same base floating point type");
+
+    // Views must have the same base floating point type
+    static_assert(
+        KokkosFFT::Impl::have_same_base_floating_point_type_v<RealViewType1,
+                                                              RealViewType2>,
+        "ViewTypes must have the same base floating point type");
+    static_assert(
+        KokkosFFT::Impl::have_same_base_floating_point_type_v<RealViewType1,
+                                                              ComplexViewType2>,
+        "ViewTypes must have the same base floating point type");
+    static_assert(
+        KokkosFFT::Impl::have_same_base_floating_point_type_v<ComplexViewType1,
+                                                              RealViewType2>,
+        "ViewTypes must have the same base floating point type");
+    static_assert(
+        KokkosFFT::Impl::have_same_base_floating_point_type_v<ComplexViewType1,
+                                                              ComplexViewType2>,
+        "ViewTypes must have the same base floating point type");
+  } else {
+    // Values must not have the same base floating point type
+    static_assert(
+        !KokkosFFT::Impl::have_same_base_floating_point_type_v<complex_type1,
+                                                               real_type2>,
+        "Values must not have the same base floating point type");
+    static_assert(
+        !KokkosFFT::Impl::have_same_base_floating_point_type_v<complex_type1,
+                                                               complex_type2>,
+        "Values must not have the same base floating point type");
+
+    // Views must not have the same base floating point type
+    static_assert(
+        !KokkosFFT::Impl::have_same_base_floating_point_type_v<RealViewType1,
+                                                               RealViewType2>,
+        "ViewTypes must not have the same base floating point type");
+    static_assert(!KokkosFFT::Impl::have_same_base_floating_point_type_v<
+                      RealViewType1, ComplexViewType2>,
+                  "ViewTypes must not have the same base floating point type");
+    static_assert(
+        !KokkosFFT::Impl::have_same_base_floating_point_type_v<ComplexViewType1,
+                                                               RealViewType2>,
+        "ViewTypes must not have the same base floating point type");
+    static_assert(!KokkosFFT::Impl::have_same_base_floating_point_type_v<
+                      ComplexViewType1, ComplexViewType2>,
+                  "ViewTypes must not have the same base floating point type");
+  }
+}
+
+template <typename LayoutType1, typename LayoutType2>
+void test_have_same_layout() {
+  using RealType  = double;
+  using ViewType1 = Kokkos::View<RealType*, LayoutType1>;
+  using ViewType2 = Kokkos::View<RealType*, LayoutType2>;
+
+  // Tests that Views have the same layout
+  if constexpr (std::is_same_v<LayoutType1, LayoutType2>) {
+    // Views must have the same layout
+    static_assert(KokkosFFT::Impl::have_same_layout_v<ViewType1, ViewType2>,
+                  "ViewTypes must have the same layout");
+  } else {
+    // Views must not have the same layout
+    static_assert(!KokkosFFT::Impl::have_same_layout_v<ViewType1, ViewType2>,
+                  "ViewTypes must not have the same layout");
+  }
+}
+
+template <typename LayoutType1, typename LayoutType2>
+void test_have_same_rank() {
+  using RealType                   = double;
+  using DynamicRank1ViewType       = Kokkos::View<RealType*, LayoutType1>;
+  using DynamicRank2ViewType       = Kokkos::View<RealType**, LayoutType1>;
+  using StaticRank1ViewType        = Kokkos::View<RealType[3], LayoutType2>;
+  using StaticRank2ViewType        = Kokkos::View<RealType[2][5], LayoutType2>;
+  using DynamicStaticRank2ViewType = Kokkos::View<RealType* [5], LayoutType1>;
+
+  // Views must have the same rank
+  static_assert(KokkosFFT::Impl::have_same_rank_v<DynamicRank1ViewType,
+                                                  StaticRank1ViewType>,
+                "ViewTypes must have the same rank");
+  static_assert(KokkosFFT::Impl::have_same_rank_v<DynamicRank2ViewType,
+                                                  StaticRank2ViewType>,
+                "ViewTypes must have the same rank");
+  static_assert(KokkosFFT::Impl::have_same_rank_v<DynamicRank2ViewType,
+                                                  DynamicStaticRank2ViewType>,
+                "ViewTypes must have the same rank");
+
+  // Views must not have the same rank
+  static_assert(!KokkosFFT::Impl::have_same_rank_v<DynamicRank1ViewType,
+                                                   DynamicRank2ViewType>,
+                "ViewTypes must not have the same rank");
+  static_assert(!KokkosFFT::Impl::have_same_rank_v<DynamicRank1ViewType,
+                                                   StaticRank2ViewType>,
+                "ViewTypes must not have the same rank");
+  static_assert(!KokkosFFT::Impl::have_same_rank_v<DynamicRank1ViewType,
+                                                   DynamicStaticRank2ViewType>,
+                "ViewTypes must not have the same rank");
+  static_assert(!KokkosFFT::Impl::have_same_rank_v<StaticRank1ViewType,
+                                                   DynamicRank2ViewType>,
+                "ViewTypes must not have the same rank");
+  static_assert(!KokkosFFT::Impl::have_same_rank_v<StaticRank1ViewType,
+                                                   StaticRank2ViewType>,
+                "ViewTypes must not have the same rank");
+  static_assert(!KokkosFFT::Impl::have_same_rank_v<StaticRank1ViewType,
+                                                   DynamicStaticRank2ViewType>,
+                "ViewTypes must not have the same rank");
+}
+
+// \brief Test if two Views are operatable
+// \tparam ExecutionSpace1 Execution space for the device
+// \tparam ExecutionSpace2 Execution space for the View memory space
+// \tparam RealType1 Base Real Value type of the View1
+// \tparam LayoutType1 Layout type of the View1
+// \tparam RealType2 Base Real Value type of the View2
+// \tparam LayoutType2 Layout type of the View2
+template <typename ExecutionSpace1, typename ExecutionSpace2,
+          typename RealType1, typename LayoutType1, typename RealType2,
+          typename LayoutType2>
+void test_are_operatable_views() {
+  using real_type1    = RealType1;
+  using real_type2    = RealType2;
+  using complex_type1 = Kokkos::complex<real_type1>;
+  using complex_type2 = Kokkos::complex<real_type2>;
+
+  using RealViewType1 = Kokkos::View<real_type1*, LayoutType1, ExecutionSpace2>;
+  using ComplexViewType1 =
+      Kokkos::View<complex_type1*, LayoutType1, ExecutionSpace2>;
+  using RealViewType2 = Kokkos::View<real_type2*, LayoutType2, ExecutionSpace2>;
+  using ComplexViewType2 =
+      Kokkos::View<complex_type2*, LayoutType2, ExecutionSpace2>;
+  using RealViewType3 =
+      Kokkos::View<real_type2**, LayoutType2, ExecutionSpace2>;
+  using ComplexViewType3 =
+      Kokkos::View<complex_type2* [3], LayoutType2, ExecutionSpace2>;
+
+  // Tests that the Views are accessible from the ExecutionSpace
+  if constexpr (Kokkos::SpaceAccessibility<
+                    ExecutionSpace1,
+                    typename RealViewType1::memory_space>::accessible) {
+    // Tests that the Views have the same base floating point type in float or
+    // double
+    if constexpr (std::is_same_v<RealType1, RealType2> &&
+                  (std::is_same_v<RealType1, float> ||
+                   std::is_same_v<RealType1, double>)) {
+      // Tests that the Views have the same layout in LayoutLeft or LayoutRight
+      if constexpr (std::is_same_v<LayoutType1, LayoutType2> &&
+                    (std::is_same_v<LayoutType1, Kokkos::LayoutLeft> ||
+                     std::is_same_v<LayoutType1, Kokkos::LayoutRight>)) {
+        // Tests that the Views are operatable if they have the same rank
+        static_assert(KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, RealViewType1, RealViewType2>,
+                      "InViewType and OutViewType must have the same rank");
+        static_assert(KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, RealViewType1, ComplexViewType2>,
+                      "InViewType and OutViewType must have the same rank");
+        static_assert(KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, ComplexViewType1, RealViewType2>,
+                      "InViewType and OutViewType must have the same rank");
+        static_assert(KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, ComplexViewType1, ComplexViewType2>,
+                      "InViewType and OutViewType must have the same rank");
+
+        // Tests that the Views are not operatable if the ranks are not the same
+        static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, RealViewType1, RealViewType3>,
+                      "InViewType and OutViewType must have the same rank");
+        static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, RealViewType1, ComplexViewType3>,
+                      "InViewType and OutViewType must have the same rank");
+        static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, ComplexViewType1, RealViewType3>,
+                      "InViewType and OutViewType must have the same rank");
+        static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, ComplexViewType1, ComplexViewType3>,
+                      "InViewType and OutViewType must have the same rank");
+      } else {
+        // Views are not operatable because they do not have the same layout in
+        // LayoutLeft or LayoutRight
+        static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, RealViewType1, RealViewType2>,
+                      "Layouts are not identical or one of them is not "
+                      "LayoutLeft or LayoutRight");
+        static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, RealViewType1, ComplexViewType2>,
+                      "Layouts are not identical or one of them is not "
+                      "LayoutLeft or LayoutRight");
+        static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, ComplexViewType1, RealViewType2>,
+                      "Layouts are not identical or one of them is not "
+                      "LayoutLeft or LayoutRight");
+        static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                          ExecutionSpace1, ComplexViewType1, ComplexViewType2>,
+                      "Layouts are not identical or one of them is not "
+                      "LayoutLeft or LayoutRight");
+      }
+    } else {
+      // Views are not operatable because they do not have the same base
+      // floating point type in float or double
+      static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                        ExecutionSpace1, RealViewType1, RealViewType2>,
+                    "Base value types are not identical or one of them is not "
+                    "float or double");
+      static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                        ExecutionSpace1, RealViewType1, ComplexViewType2>,
+                    "Base value types are not identical or one of them is not "
+                    "float or double");
+      static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                        ExecutionSpace1, ComplexViewType1, RealViewType2>,
+                    "Base value types are not identical or one of them is not "
+                    "float or double");
+      static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                        ExecutionSpace1, ComplexViewType1, ComplexViewType2>,
+                    "Base value types are not identical or one of them is not "
+                    "float or double");
+    }
+  } else {
+    // Views are not operatable because they are not accessible from
+    // ExecutionSpace
+    static_assert(
+        !KokkosFFT::Impl::are_operatable_views_v<ExecutionSpace1, RealViewType1,
+                                                 RealViewType2>,
+        "Either InViewType or OutViewType is not accessible from "
+        "ExecutionSpace");
+    static_assert(
+        !KokkosFFT::Impl::are_operatable_views_v<ExecutionSpace1, RealViewType1,
+                                                 ComplexViewType2>,
+        "Either InViewType or OutViewType is not accessible from "
+        "ExecutionSpace");
+    static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                      ExecutionSpace1, ComplexViewType1, RealViewType2>,
+                  "Either InViewType or OutViewType is not accessible from "
+                  "ExecutionSpace");
+    static_assert(!KokkosFFT::Impl::are_operatable_views_v<
+                      ExecutionSpace1, ComplexViewType1, ComplexViewType2>,
+                  "Either InViewType or OutViewType is not accessible from "
+                  "ExecutionSpace");
+  }
+}
+
+TYPED_TEST(PairedValueTypes, have_same_base_floating_point_type) {
+  using real_type1 = typename TestFixture::real_type1;
+  using real_type2 = typename TestFixture::real_type2;
+
+  test_have_same_base_floating_point_type<real_type1, real_type2>();
+}
+
+TYPED_TEST(PairedLayoutTypes, have_same_layout) {
+  using layout_type1 = typename TestFixture::layout_type1;
+  using layout_type2 = typename TestFixture::layout_type2;
+
+  test_have_same_layout<layout_type1, layout_type2>();
+}
+
+TYPED_TEST(PairedLayoutTypes, have_same_rank) {
+  using layout_type1 = typename TestFixture::layout_type1;
+  using layout_type2 = typename TestFixture::layout_type2;
+
+  test_have_same_rank<layout_type1, layout_type2>();
+}
+
+TYPED_TEST(PairedViewTypes, are_operatable_views) {
+  using real_type1   = typename TestFixture::real_type1;
+  using layout_type1 = typename TestFixture::layout_type1;
+  using real_type2   = typename TestFixture::real_type2;
+  using layout_type2 = typename TestFixture::layout_type2;
+  using host_space   = Kokkos::DefaultHostExecutionSpace;
+  using device_space = Kokkos::DefaultExecutionSpace;
+
+  test_are_operatable_views<host_space, host_space, real_type1, layout_type1,
+                            real_type2, layout_type2>();
+  test_are_operatable_views<host_space, device_space, real_type1, layout_type1,
+                            real_type2, layout_type2>();
+  test_are_operatable_views<device_space, host_space, real_type1, layout_type1,
+                            real_type2, layout_type2>();
+  test_are_operatable_views<device_space, device_space, real_type1,
+                            layout_type1, real_type2, layout_type2>();
+}
diff --git a/common/unit_test/Test_Utils.hpp b/common/unit_test/Test_Utils.hpp
index d30aa9b0..28fe30ca 100644
--- a/common/unit_test/Test_Utils.hpp
+++ b/common/unit_test/Test_Utils.hpp
@@ -5,6 +5,9 @@
 #ifndef TEST_UTILS_HPP
 #define TEST_UTILS_HPP
 
+#include <gtest/gtest.h>
+#include <tuple>
+#include <type_traits>
 #include "Test_Types.hpp"
 
 template <typename AViewType, typename BViewType>
@@ -54,4 +57,95 @@ void display(std::string name, std::vector<T>& values) {
   }
 }
 
-#endif
\ No newline at end of file
+/// Transform a sequence S to a tuple:
+/// - a std::integer_sequence<T, Ints...> to a
+/// std::tuple<std::integral_constant<T, Ints>...>
+/// - a std::pair<T, U> to a std::tuple<T, U>
+/// - identity otherwise (std::tuple)
+template <class S>
+struct to_tuple {
+  using type = S;
+};
+
+template <class T, T... Ints>
+struct to_tuple<std::integer_sequence<T, Ints...>> {
+  using type = std::tuple<std::integral_constant<T, Ints>...>;
+};
+
+template <class T, class U>
+struct to_tuple<std::pair<T, U>> {
+  using type = std::tuple<T, U>;
+};
+
+template <class S>
+using to_tuple_t = typename to_tuple<S>::type;
+
+template <class TupleOfTuples, class Tuple>
+struct for_each_tuple_cat;
+
+template <class... Tuples, class Tuple>
+struct for_each_tuple_cat<std::tuple<Tuples...>, Tuple> {
+  using type = std::tuple<decltype(std::tuple_cat(std::declval<Tuples>(),
+                                                  std::declval<Tuple>()))...>;
+};
+
+/// Construct a tuple of tuples that is the result of the concatenation of the
+/// tuples in TupleOfTuples with Tuple.
+template <class TupleOfTuples, class Tuple>
+using for_each_tuple_cat_t =
+    typename for_each_tuple_cat<TupleOfTuples, Tuple>::type;
+
+static_assert(
+    std::is_same_v<for_each_tuple_cat_t<std::tuple<std::tuple<double, double>,
+                                                   std::tuple<int, double>>,
+                                        std::tuple<int>>,
+                   std::tuple<std::tuple<double, double, int>,
+                              std::tuple<int, double, int>>>);
+
+static_assert(
+    std::is_same_v<for_each_tuple_cat_t<std::tuple<std::tuple<double, double>>,
+                                        std::tuple<int>>,
+                   std::tuple<std::tuple<double, double, int>>>);
+
+template <class InTupleOfTuples, class OutTupleOfTuples>
+struct cartesian_product_impl;
+
+template <class... HeadArgs, class... TailTuples, class OutTupleOfTuples>
+struct cartesian_product_impl<
+    std::tuple<std::tuple<HeadArgs...>, TailTuples...>, OutTupleOfTuples>
+    : cartesian_product_impl<
+          std::tuple<TailTuples...>,
+          decltype(std::tuple_cat(
+              std::declval<for_each_tuple_cat_t<OutTupleOfTuples,
+                                                std::tuple<HeadArgs>>>()...))> {
+};
+
+template <class OutTupleOfTuples>
+struct cartesian_product_impl<std::tuple<>, OutTupleOfTuples> {
+  using type = OutTupleOfTuples;
+};
+
+/// Generate a std::tuple cartesian product from multiple tuple-like structures
+/// (std::tuple, std::integer_sequence and std::pair) Do not rely on the
+/// ordering result.
+template <class... InTuplesLike>
+using cartesian_product_t =
+    typename cartesian_product_impl<std::tuple<to_tuple_t<InTuplesLike>...>,
+                                    std::tuple<std::tuple<>>>::type;
+
+/// Transform a std::tuple<Args...> to a testing::Types<Args...>, identity
+/// otherwise
+template <class T>
+struct tuple_to_types {
+  using type = T;
+};
+
+template <class... Args>
+struct tuple_to_types<std::tuple<Args...>> {
+  using type = testing::Types<Args...>;
+};
+
+template <class T>
+using tuple_to_types_t = typename tuple_to_types<T>::type;
+
+#endif