GPU: Centralize expression type traits

libeigen/eigen!2799

diff --git a/unsupported/Eigen/GPU b/unsupported/Eigen/GPU
index 06dcd0a..4128fd9 100644
--- a/unsupported/Eigen/GPU
+++ b/unsupported/Eigen/GPU
@@ -43,6 +43,8 @@
 #ifdef EIGEN_USE_GPU
 // IWYU pragma: begin_exports
 #include "src/GPU/FwdDecl.h"
+#include "src/GPU/Meta.h"
+#include "src/GPU/type_traits.h"
 #include "src/GPU/DeviceScalar.h"
 #include "src/GPU/DeviceMatrix.h"
 #include "src/GPU/GpuContext.h"
diff --git a/unsupported/Eigen/src/GPU/DeviceExpr.h b/unsupported/Eigen/src/GPU/DeviceExpr.h
index 9084001..f0880f4 100644
--- a/unsupported/Eigen/src/GPU/DeviceExpr.h
+++ b/unsupported/Eigen/src/GPU/DeviceExpr.h
@@ -27,30 +27,11 @@
 #include "./InternalHeaderCheck.h"
 
 #include "./CuBlasSupport.h"
-#include "./FwdDecl.h"
+#include "./type_traits.h"
 
 namespace Eigen {
 namespace gpu {
 namespace internal {
-// Forward declaration — specializations follow below, after the class definitions.
-template <typename Expr>
-struct device_expr_traits;
-
-// Shorthand for the scalar type of a device expression.
-template <typename Expr>
-using scalar_type_t = typename device_expr_traits<Expr>::scalar_type;
-}  // namespace internal
-
-namespace internal {
-// Identifies gpu::DeviceScalar so the generic scalar-times-matrix overloads
-// below can exclude it (DeviceScalar has dedicated device-pointer overloads
-// and is implicitly convertible to its host scalar, which would otherwise
-// make the overload sets ambiguous).
-template <typename T>
-struct is_device_scalar : std::false_type {};
-template <typename S>
-struct is_device_scalar<DeviceScalar<S>> : std::true_type {};
-
 // SFINAE gate for scalar factors: any type convertible to the expression's
 // scalar (so `2 * d_A` and `2.0 * d_cplx` work), except DeviceScalar.
 template <typename T, typename S>
@@ -60,8 +41,7 @@
 
 }  // namespace internal
 
-// Returned by DeviceMatrix::adjoint(). Maps to cublasXgemm transA/B = C.
-
+/** \brief View returned by DeviceMatrix::adjoint(); maps to the cuBLAS conjugate-transpose operand flag. */
 template <typename Scalar_>
 class AdjointView {
  public:
@@ -73,8 +53,7 @@
   const DeviceMatrix<Scalar>& mat_;
 };
 
-// Returned by DeviceMatrix::transpose(). Maps to cublasXgemm transA/B = T.
-
+/** \brief View returned by DeviceMatrix::transpose(); maps to the cuBLAS transpose operand flag. */
 template <typename Scalar_>
 class TransposeView {
  public:
@@ -86,23 +65,29 @@
   const DeviceMatrix<Scalar>& mat_;
 };
 
-// Returned by operator*(Scalar, DeviceMatrix/View). Carries the scalar factor.
-
-template <typename Inner>
+/** \brief Expression returned by operator*(Scalar, DeviceMatrix/View), carrying the scalar factor.
+ *
+ * \c Inner names the scaled operand, decayed, for callers that need to reason
+ * about what is being scaled. The is_scaled_* predicates in type_traits.h
+ * deliberately do not read it: naming a member instantiates Scaled, and
+ * Scaled<GemmExpr<...>>::Scalar is ill-formed. They deduce the operand from the
+ * template-id instead.
+ */
+template <typename Inner_>
 class Scaled {
  public:
+  using Inner = std::decay_t<Inner_>;
   using Scalar = internal::scalar_type_t<Inner>;
-  Scaled(Scalar alpha, const Inner& inner) : alpha_(alpha), inner_(inner) {}
+  Scaled(Scalar alpha, const Inner_& inner) : alpha_(alpha), inner_(inner) {}
   Scalar scalar() const { return alpha_; }
-  const Inner& inner() const { return inner_; }
+  const Inner_& inner() const { return inner_; }
 
  private:
   Scalar alpha_;
-  const Inner& inner_;
+  const Inner_& inner_;
 };
 
-// Returned by operator*(lhs_expr, rhs_expr). Dispatches to cuBLAS GEMM.
-
+/** \brief Expression returned by operator*(lhs_expr, rhs_expr), dispatched to cuBLAS GEMM. */
 template <typename Lhs, typename Rhs>
 class GemmExpr {
  public:
@@ -183,7 +168,7 @@
 }
 
 namespace internal {
-// Default: a DeviceMatrix is NoTrans.
+// Default: a DeviceMatrix is NoTrans. Documented on the FwdDecl.h forward declaration.
 template <typename T>
 struct device_expr_traits {
   static constexpr bool is_device_expr = false;
@@ -236,9 +221,12 @@
   return {a, b};
 }
 
-// Like Scaled but carries a DeviceScalar (device pointer) instead of
-// a host scalar. operator+= dispatches to cuBLAS axpy with POINTER_MODE_DEVICE.
-
+/**
+ * \brief Expression that scales a device matrix by a DeviceScalar.
+ *
+ * Unlike Scaled, this expression carries a device pointer. operator+= dispatches to cuBLAS AXPY with device pointer
+ * mode.
+ */
 template <typename Scalar_>
 class DeviceScaledDevice {
  public:
@@ -267,6 +255,7 @@
 // If DeviceMatrix is ever made an Eigen expression type, these would need to
 // be revisited.
 
+/** \brief Linear combination of two device matrices. */
 template <typename Scalar_>
 class DeviceAddExpr {
  public:
diff --git a/unsupported/Eigen/src/GPU/DeviceScalar.h b/unsupported/Eigen/src/GPU/DeviceScalar.h
index 8652b91..e24d122 100644
--- a/unsupported/Eigen/src/GPU/DeviceScalar.h
+++ b/unsupported/Eigen/src/GPU/DeviceScalar.h
@@ -28,6 +28,7 @@
 namespace Eigen {
 namespace gpu {
 
+/** \brief RAII wrapper for a scalar in GPU device memory. */
 template <typename Scalar_>
 class DeviceScalar {
  public:
diff --git a/unsupported/Eigen/src/GPU/FwdDecl.h b/unsupported/Eigen/src/GPU/FwdDecl.h
index a8b74d9..9cf0457 100644
--- a/unsupported/Eigen/src/GPU/FwdDecl.h
+++ b/unsupported/Eigen/src/GPU/FwdDecl.h
@@ -18,6 +18,22 @@
 
 class Context;
 
+enum class GpuOp;
+
+namespace internal {
+class DeviceBuffer;
+
+/** \brief Describes GPU device expression types.
+ *
+ * Specializations report the expression's scalar type, its transpose op, and
+ * how to reach the underlying matrix and deferred scalar. They live in
+ * DeviceExpr.h; this declaration is what the query aliases in type_traits.h
+ * are written against.
+ */
+template <typename Expr>
+struct device_expr_traits;
+}  // namespace internal
+
 template <typename Scalar_>
 class DeviceMatrix;
 template <typename Scalar_>
@@ -27,6 +43,8 @@
 class LLT;
 template <typename Scalar_>
 class LU;
+template <typename Scalar_>
+class QR;
 
 template <typename Scalar_>
 class AdjointView;
diff --git a/unsupported/Eigen/src/GPU/GpuSupport.h b/unsupported/Eigen/src/GPU/GpuSupport.h
index cf7c90a..5027564 100644
--- a/unsupported/Eigen/src/GPU/GpuSupport.h
+++ b/unsupported/Eigen/src/GPU/GpuSupport.h
@@ -214,6 +214,7 @@
   }
 };
 
+/** \brief Internal RAII owner for an untyped GPU device allocation. */
 class DeviceBuffer {
  public:
   DeviceBuffer() = default;
diff --git a/unsupported/Eigen/src/GPU/Meta.h b/unsupported/Eigen/src/GPU/Meta.h
index 21895d2..c483220 100644
--- a/unsupported/Eigen/src/GPU/Meta.h
+++ b/unsupported/Eigen/src/GPU/Meta.h
@@ -30,6 +30,9 @@
 template <typename T1, typename T2>
 using require_not_same_t = require_not_t<std::is_same<std::decay_t<T1>, std::decay_t<T2>>>;
 
+template <bool... Values>
+using require_all_t = require_t<Eigen::internal::reduce_all<Values...>>;
+
 }  // namespace internal
 }  // namespace gpu
 }  // namespace Eigen
diff --git a/unsupported/Eigen/src/GPU/type_traits.h b/unsupported/Eigen/src/GPU/type_traits.h
new file mode 100644
index 0000000..ae9f81d
--- /dev/null
+++ b/unsupported/Eigen/src/GPU/type_traits.h
@@ -0,0 +1,954 @@
+// This file is part of Eigen, a lightweight C++ template library
+// for linear algebra.
+//
+// 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/.
+// SPDX-FileCopyrightText: The Eigen Authors
+// SPDX-License-Identifier: MPL-2.0
+
+#ifndef EIGEN_GPU_TYPE_TRAITS_H
+#define EIGEN_GPU_TYPE_TRAITS_H
+
+// IWYU pragma: private
+#include "./InternalHeaderCheck.h"
+
+#include "./FwdDecl.h"
+#include "./Meta.h"
+
+#include <complex>
+#include <type_traits>
+
+namespace Eigen {
+namespace gpu {
+
+/**
+ * @name internal:: type traits
+ *
+ * Each one keys off the *name* of a forward-declared class. None of them
+ * inspect members so they parse cleanly with FwdDecl.h alone. is_scaled_leaf
+ * and is_scaled_gemm compose with an inner predicate, but still by matching the
+ * template-id rather than by reading a member.
+ */
+
+namespace internal {
+
+///@{
+
+template <typename Expr>
+using scalar_type_t = typename device_expr_traits<Expr>::scalar_type;
+
+template <typename T>
+struct is_device_buffer : Eigen::internal::bool_constant<false> {};
+template <>
+struct is_device_buffer<DeviceBuffer> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_device_matrix : Eigen::internal::bool_constant<false> {};
+template <typename Scalar>
+struct is_device_matrix<DeviceMatrix<Scalar>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_gemm_expr : Eigen::internal::bool_constant<false> {};
+template <typename Lhs, typename Rhs>
+struct is_gemm_expr<GemmExpr<Lhs, Rhs>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_adjoint_view : Eigen::internal::bool_constant<false> {};
+template <typename Scalar>
+struct is_adjoint_view<AdjointView<Scalar>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_transpose_view : Eigen::internal::bool_constant<false> {};
+template <typename Scalar>
+struct is_transpose_view<TransposeView<Scalar>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_scaled : Eigen::internal::bool_constant<false> {};
+template <typename Inner>
+struct is_scaled<Scaled<Inner>> : Eigen::internal::bool_constant<true> {};
+
+/**
+ * @brief Detects a Scaled directly over a leaf DeviceMatrix (no view in between).
+ * @tparam T The type to test.
+ * @note Defers to is_device_matrix on the scaled operand rather than restating
+ * the nested pattern, so it tracks whatever is_device_matrix accepts.
+ * @note The operand is deduced from the template-id, not read from Scaled::Inner:
+ * naming a member would instantiate Scaled, and Scaled<GemmExpr<...>>::Scalar is
+ * ill-formed because GemmExpr has no device_expr_traits specialization. Matching
+ * the template-id keeps this predicate usable on any Scaled, complete or not.
+ * @note Such a node arriving as a donation (owned rvalue) is materialized by
+ * stealing the leaf and applying the scalar in place (scal) instead of a geam
+ * into a fresh buffer.
+ */
+template <typename T>
+struct is_scaled_leaf : Eigen::internal::bool_constant<false> {};
+template <typename Inner>
+struct is_scaled_leaf<Scaled<Inner>> : is_device_matrix<std::decay_t<Inner>> {};
+
+/**
+ * @brief Detects a Scaled directly over a GemmExpr (a product carrying ONE deferred scalar).
+ * @tparam T The type to test.
+ * @note Defers to is_gemm_expr on the scaled operand, deduced the same way as in
+ * is_scaled_leaf and for the same reason.
+ * @note Such a summand routes through the GEMM epilogue with its factor as the
+ * gemm's alpha_scale (no temporary).
+ */
+template <typename T>
+struct is_scaled_gemm : Eigen::internal::bool_constant<false> {};
+template <typename Inner>
+struct is_scaled_gemm<Scaled<Inner>> : is_gemm_expr<std::decay_t<Inner>> {};
+
+template <typename T>
+struct is_triangular_view : Eigen::internal::bool_constant<false> {};
+template <typename Inner, int UpLo>
+struct is_triangular_view<TriangularView<Inner, UpLo>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_trsm_expr : Eigen::internal::bool_constant<false> {};
+template <typename Scalar, int UpLo>
+struct is_trsm_expr<TrsmExpr<Scalar, UpLo>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_selfadjoint_view : Eigen::internal::bool_constant<false> {};
+template <typename Scalar, int UpLo>
+struct is_selfadjoint_view<SelfAdjointView<Scalar, UpLo>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_const_selfadjoint_view : Eigen::internal::bool_constant<false> {};
+template <typename Inner, int UpLo>
+struct is_const_selfadjoint_view<ConstSelfAdjointView<Inner, UpLo>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_symm_expr : Eigen::internal::bool_constant<false> {};
+template <typename Scalar, int UpLo>
+struct is_symm_expr<SymmExpr<Scalar, UpLo>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_syrk_expr : Eigen::internal::bool_constant<false> {};
+template <typename A, int UpLo>
+struct is_syrk_expr<SyrkExpr<A, UpLo>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_llt_solve_expr : Eigen::internal::bool_constant<false> {};
+template <typename Scalar, int UpLo>
+struct is_llt_solve_expr<LltSolveExpr<Scalar, UpLo>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_lu_solve_expr : Eigen::internal::bool_constant<false> {};
+template <typename Scalar>
+struct is_lu_solve_expr<LuSolveExpr<Scalar>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_llt_view : Eigen::internal::bool_constant<false> {};
+template <typename Scalar, int UpLo>
+struct is_llt_view<LLTView<Scalar, UpLo>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_lu_view : Eigen::internal::bool_constant<false> {};
+template <typename Scalar>
+struct is_lu_view<LUView<Scalar>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_device_add_expr : Eigen::internal::bool_constant<false> {};
+template <typename Scalar>
+struct is_device_add_expr<DeviceAddExpr<Scalar>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_device_scaled_device : Eigen::internal::bool_constant<false> {};
+template <typename Inner>
+struct is_device_scaled_device<DeviceScaledDevice<Inner>> : Eigen::internal::bool_constant<true> {};
+
+template <typename T>
+struct is_device_scalar : Eigen::internal::bool_constant<false> {};
+template <typename S>
+struct is_device_scalar<DeviceScalar<S>> : Eigen::internal::bool_constant<true> {};
+
+///@}
+
+}  // namespace internal
+
+/**
+ * @defgroup gpu_type_traits GPU type traits
+ * @name Public is_* / is_*_v / require_* / require_all_* wrappers
+ *
+ * Type traits for the Eigen GPU library
+ */
+///@{
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::internal::DeviceBuffer
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_device_buffer : internal::is_device_buffer<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::internal::DeviceBuffer
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_device_buffer_v = is_device_buffer<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::internal::DeviceBuffer
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_device_buffer = internal::require_t<is_device_buffer<T>>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::DeviceMatrix
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_device_matrix : internal::is_device_matrix<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::DeviceMatrix
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_device_matrix_v = is_device_matrix<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::DeviceMatrix
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_device_matrix = internal::require_t<is_device_matrix<T>>;
+
+/**
+ * Require a type is not a @ref Eigen::gpu::DeviceMatrix
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_not_device_matrix = internal::require_not_t<is_device_matrix<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::DeviceMatrix types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_device_matrix = internal::require_all_t<is_device_matrix_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::GemmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_gemm_expr : internal::is_gemm_expr<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::GemmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_gemm_expr_v = is_gemm_expr<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::GemmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_gemm_expr = internal::require_t<is_gemm_expr<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::GemmExpr types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_gemm_expr = internal::require_all_t<is_gemm_expr_v<Types>...>;
+
+/**
+ * Detect if a type is an @ref Eigen::gpu::AdjointView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_adjoint_view : internal::is_adjoint_view<std::decay_t<T>> {};
+
+/**
+ * True iff a type is an @ref Eigen::gpu::AdjointView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_adjoint_view_v = is_adjoint_view<T>::value;
+
+/**
+ * Require a type is an @ref Eigen::gpu::AdjointView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_adjoint_view = internal::require_t<is_adjoint_view<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::AdjointView types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_adjoint_view = internal::require_all_t<is_adjoint_view_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::TransposeView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_transpose_view : internal::is_transpose_view<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::TransposeView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_transpose_view_v = is_transpose_view<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::TransposeView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_transpose_view = internal::require_t<is_transpose_view<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::TransposeView types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_transpose_view = internal::require_all_t<is_transpose_view_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::Scaled expression.
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_scaled : internal::is_scaled<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::Scaled expression.
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_scaled_v = is_scaled<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::Scaled expression.
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_scaled = internal::require_t<is_scaled<T>>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::Scaled expression directly over a @ref Eigen::gpu::DeviceMatrix
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_scaled_leaf : internal::is_scaled_leaf<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::Scaled expression directly over a @ref Eigen::gpu::DeviceMatrix
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_scaled_leaf_v = is_scaled_leaf<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::Scaled expression directly over a @ref Eigen::gpu::DeviceMatrix
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_scaled_leaf = internal::require_t<is_scaled_leaf<T>>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::Scaled expression directly over a @ref Eigen::gpu::GemmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_scaled_gemm : internal::is_scaled_gemm<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::Scaled expression directly over a @ref Eigen::gpu::GemmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_scaled_gemm_v = is_scaled_gemm<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::Scaled expression directly over a @ref Eigen::gpu::GemmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_scaled_gemm = internal::require_t<is_scaled_gemm<T>>;
+
+/**
+ * True iff a type is a @ref Eigen::gpu::GemmExpr or a @ref Eigen::gpu::Scaled expression directly over one.
+ * @tparam T The type to test.
+ * @note Such a product may carry one deferred scalar, which becomes the GEMM alpha scale.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_gemm_like_v = is_gemm_expr_v<T> || is_scaled_gemm_v<T>;
+
+/**
+ * Require a type is an @ref Eigen::gpu::AdjointView or a @ref Eigen::gpu::TransposeView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_adjoint_or_transpose_view =
+    internal::require_t<Eigen::internal::bool_constant<is_adjoint_view_v<T> || is_transpose_view_v<T>>>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::TriangularView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_triangular_view : internal::is_triangular_view<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::TriangularView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_triangular_view_v = is_triangular_view<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::TriangularView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_triangular_view = internal::require_t<is_triangular_view<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::TriangularView types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_triangular_view = internal::require_all_t<is_triangular_view_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::TrsmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_trsm_expr : internal::is_trsm_expr<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::TrsmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_trsm_expr_v = is_trsm_expr<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::TrsmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_trsm_expr = internal::require_t<is_trsm_expr<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::TrsmExpr types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_trsm_expr = internal::require_all_t<is_trsm_expr_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::SelfAdjointView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_selfadjoint_view : internal::is_selfadjoint_view<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::SelfAdjointView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_selfadjoint_view_v = is_selfadjoint_view<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::SelfAdjointView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_selfadjoint_view = internal::require_t<is_selfadjoint_view<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::SelfAdjointView types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_selfadjoint_view = internal::require_all_t<is_selfadjoint_view_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::ConstSelfAdjointView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_const_selfadjoint_view : internal::is_const_selfadjoint_view<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::ConstSelfAdjointView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_const_selfadjoint_view_v = is_const_selfadjoint_view<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::ConstSelfAdjointView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_const_selfadjoint_view = internal::require_t<is_const_selfadjoint_view<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::ConstSelfAdjointView types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_const_selfadjoint_view = internal::require_all_t<is_const_selfadjoint_view_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::SymmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_symm_expr : internal::is_symm_expr<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::SymmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_symm_expr_v = is_symm_expr<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::SymmExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_symm_expr = internal::require_t<is_symm_expr<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::SymmExpr types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_symm_expr = internal::require_all_t<is_symm_expr_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::SyrkExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_syrk_expr : internal::is_syrk_expr<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::SyrkExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_syrk_expr_v = is_syrk_expr<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::SyrkExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_syrk_expr = internal::require_t<is_syrk_expr<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::SyrkExpr types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_syrk_expr = internal::require_all_t<is_syrk_expr_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::LltSolveExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_llt_solve_expr : internal::is_llt_solve_expr<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::LltSolveExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_llt_solve_expr_v = is_llt_solve_expr<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::LltSolveExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_llt_solve_expr = internal::require_t<is_llt_solve_expr<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::LltSolveExpr types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_llt_solve_expr = internal::require_all_t<is_llt_solve_expr_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::LuSolveExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_lu_solve_expr : internal::is_lu_solve_expr<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::LuSolveExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_lu_solve_expr_v = is_lu_solve_expr<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::LuSolveExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_lu_solve_expr = internal::require_t<is_lu_solve_expr<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::LuSolveExpr types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_lu_solve_expr = internal::require_all_t<is_lu_solve_expr_v<Types>...>;
+
+/**
+ * Detect if a type is an @ref Eigen::gpu::LLTView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_llt_view : internal::is_llt_view<std::decay_t<T>> {};
+
+/**
+ * True iff a type is an @ref Eigen::gpu::LLTView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_llt_view_v = is_llt_view<T>::value;
+
+/**
+ * Require a type is an @ref Eigen::gpu::LLTView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_llt_view = internal::require_t<is_llt_view<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::LLTView types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_llt_view = internal::require_all_t<is_llt_view_v<Types>...>;
+
+/**
+ * Detect if a type is an @ref Eigen::gpu::LUView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_lu_view : internal::is_lu_view<std::decay_t<T>> {};
+
+/**
+ * True iff a type is an @ref Eigen::gpu::LUView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_lu_view_v = is_lu_view<T>::value;
+
+/**
+ * Require a type is an @ref Eigen::gpu::LUView
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_lu_view = internal::require_t<is_lu_view<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::LUView types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_lu_view = internal::require_all_t<is_lu_view_v<Types>...>;
+
+/**
+ * True iff a type is an @ref Eigen::gpu::LLTView or an @ref Eigen::gpu::LUView
+ * @tparam T The type to test.
+ * @note Arithmetic operators exclude factorization handles so they cannot be lowered as matrix operands.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_factor_expr_v = is_lu_view_v<T> || is_llt_view_v<T>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::DeviceAddExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_device_add_expr : internal::is_device_add_expr<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::DeviceAddExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_device_add_expr_v = is_device_add_expr<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::DeviceAddExpr
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_device_add_expr = internal::require_t<is_device_add_expr<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::DeviceAddExpr types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_device_add_expr = internal::require_all_t<is_device_add_expr_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::DeviceScaledDevice
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_device_scaled_device : internal::is_device_scaled_device<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::DeviceScaledDevice
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_device_scaled_device_v = is_device_scaled_device<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::DeviceScaledDevice
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_device_scaled_device = internal::require_t<is_device_scaled_device<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::DeviceScaledDevice types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_device_scaled_device = internal::require_all_t<is_device_scaled_device_v<Types>...>;
+
+/**
+ * Detect if a type is a @ref Eigen::gpu::DeviceScalar
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_device_scalar : internal::is_device_scalar<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a @ref Eigen::gpu::DeviceScalar
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_device_scalar_v = is_device_scalar<T>::value;
+
+/**
+ * Require a type is a @ref Eigen::gpu::DeviceScalar
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_device_scalar = internal::require_t<is_device_scalar<T>>;
+
+/**
+ * Require all types are @ref Eigen::gpu::DeviceScalar types.
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_device_scalar = internal::require_all_t<is_device_scalar_v<Types>...>;
+
+/**
+ * @name device_expr_traits query aliases
+ *
+ * Lazy readers of the member-inspecting internal::device_expr_traits, whose
+ * specializations live in DeviceExpr.h. Declared from the FwdDecl.h forward
+ * declaration; they resolve only when instantiated on a complete specialization.
+ */
+///@{
+
+template <typename T>
+using scalar_type_t = internal::scalar_type_t<std::decay_t<T>>;
+
+/**
+ * Detect if a type is marked as a device expression by
+ * @ref Eigen::gpu::internal::device_expr_traits
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_device_expr : Eigen::internal::bool_constant<internal::device_expr_traits<std::decay_t<T>>::is_device_expr> {
+};
+
+/**
+ * True iff a type is marked as a device expression by
+ * @ref Eigen::gpu::internal::device_expr_traits
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_device_expr_v = is_device_expr<T>::value;
+
+/**
+ * Require a type is marked as a device expression by
+ * @ref Eigen::gpu::internal::device_expr_traits
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_device_expr = internal::require_t<is_device_expr<T>>;
+
+/**
+ * Require all types are marked as device expressions by
+ * @ref Eigen::gpu::internal::device_expr_traits
+ * @tparam Types The types to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename... Types>
+using require_all_device_expr = internal::require_all_t<is_device_expr_v<Types>...>;
+
+template <typename T>
+constexpr GpuOp trans_op = internal::device_expr_traits<std::decay_t<T>>::op;
+
+///@}
+
+namespace internal {
+template <typename T>
+struct is_complex : Eigen::internal::bool_constant<false> {};
+
+template <typename T>
+struct is_complex<std::complex<T>> : Eigen::internal::bool_constant<true> {};
+}  // namespace internal
+
+/**
+ * Detect if a type is a `std::complex` specialization.
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_complex : internal::is_complex<std::decay_t<T>> {};
+
+/**
+ * True iff a type is a `std::complex` specialization.
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+constexpr bool is_complex_v = is_complex<T>::value;
+
+/**
+ * Detect if a type is an integral, floating-point, or `std::complex` host scalar.
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+struct is_host_scalar : Eigen::internal::bool_constant<std::is_floating_point<std::decay_t<T>>::value ||
+                                                       std::is_integral<std::decay_t<T>>::value || is_complex_v<T>> {};
+
+/**
+ * Require a type is an integral, floating-point, or `std::complex` host scalar.
+ * @tparam T The type to test.
+ * @ingroup gpu_type_traits
+ */
+template <typename T>
+using require_host_scalar = internal::require_t<is_host_scalar<T>>;
+
+///@}
+
+}  // namespace gpu
+}  // namespace Eigen
+
+#endif  // EIGEN_GPU_TYPE_TRAITS_H
diff --git a/unsupported/test/GPU/CMakeLists.txt b/unsupported/test/GPU/CMakeLists.txt
index c5375e8..3568e33 100644
--- a/unsupported/test/GPU/CMakeLists.txt
+++ b/unsupported/test/GPU/CMakeLists.txt
@@ -73,6 +73,11 @@
   endforeach()
 endfunction()
 
+# Compile-time GPU type-trait surface (static_assert only; links like device_matrix
+# because it pulls the full <unsupported/Eigen/GPU> umbrella).
+ei_add_gpu_test(device_traits
+  EXTRA_LIBS CUDA::cublas CUDA::cusolver CUDA::npps CUDA::nppc)
+
 # DeviceMatrix core: CUDA runtime + cuBLAS + cuSOLVER (for BLAS-1 ops via GpuContext).
 ei_add_gpu_test(device_matrix
   EXTRA_LIBS CUDA::cublas CUDA::cusolver CUDA::npps CUDA::nppc)
diff --git a/unsupported/test/GPU/device_traits.cpp b/unsupported/test/GPU/device_traits.cpp
new file mode 100644
index 0000000..f2d4e1a
--- /dev/null
+++ b/unsupported/test/GPU/device_traits.cpp
@@ -0,0 +1,145 @@
+// This file is part of Eigen, a lightweight C++ template library
+// for linear algebra.
+//
+// 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/.
+// SPDX-FileCopyrightText: The Eigen Authors
+// SPDX-License-Identifier: MPL-2.0
+
+// Compile-time tests for the unsupported GPU type-trait surface: name-keyed
+// predicates plus the device_expr_traits query aliases (scalar_type_t, is_device_expr_v, trans_op).
+
+#define EIGEN_USE_GPU
+#include "main.h"
+#include <type_traits>
+#include <unsupported/Eigen/GPU>
+
+using namespace Eigen;
+
+namespace {
+using TraitDeviceMatrix = gpu::DeviceMatrix<double>;
+using TraitAdjointView = gpu::AdjointView<double>;
+using TraitTransposeView = gpu::TransposeView<double>;
+using TraitScaledMatrix = gpu::Scaled<TraitDeviceMatrix>;
+using TraitGemmExpr = gpu::GemmExpr<TraitDeviceMatrix, TraitDeviceMatrix>;
+using TraitTriangularView = gpu::TriangularView<double, Lower>;
+using TraitTrsmExpr = gpu::TrsmExpr<double, Lower>;
+using TraitSelfAdjointView = gpu::SelfAdjointView<double, Lower>;
+using TraitConstSelfAdjointView = gpu::ConstSelfAdjointView<double, Lower>;
+using TraitSymmExpr = gpu::SymmExpr<double, Lower>;
+using TraitSyrkExpr = gpu::SyrkExpr<double, Lower>;
+using TraitLltSolveExpr = gpu::LltSolveExpr<double, Lower>;
+using TraitLuSolveExpr = gpu::LuSolveExpr<double>;
+using TraitLltView = gpu::LLTView<double, Lower>;
+using TraitLuView = gpu::LUView<double>;
+using TraitDeviceAddExpr = gpu::DeviceAddExpr<double>;
+using TraitDeviceScaledDevice = gpu::DeviceScaledDevice<double>;
+using TraitDeviceScalar = gpu::DeviceScalar<double>;
+using TraitDeviceBuffer = gpu::internal::DeviceBuffer;
+
+#define EIGEN_GPU_STATIC_ASSERT_TRAIT(Trait, Require, RequireAll, Type, NegativeType)       \
+  static_assert(gpu::Trait<Type>::value, #Trait " should accept the exact type");           \
+  static_assert(gpu::Trait<const Type&>::value, #Trait " should decay const references");   \
+  static_assert(gpu::Trait##_v<Type&&>, #Trait "_v should decay rvalue references");        \
+  static_assert(!gpu::Trait<NegativeType>::value, #Trait " should reject unrelated types"); \
+  static_assert(std::is_same<gpu::Require<Type>, int>::value, #Require " should compile");  \
+  static_assert(std::is_same<gpu::RequireAll<Type, const Type&>, int>::value, #RequireAll " should compile")
+
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_device_matrix, require_device_matrix, require_all_device_matrix, TraitDeviceMatrix,
+                              TraitAdjointView);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_adjoint_view, require_adjoint_view, require_all_adjoint_view, TraitAdjointView,
+                              TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_transpose_view, require_transpose_view, require_all_transpose_view, TraitTransposeView,
+                              TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_gemm_expr, require_gemm_expr, require_all_gemm_expr, TraitGemmExpr, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_triangular_view, require_triangular_view, require_all_triangular_view,
+                              TraitTriangularView, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_trsm_expr, require_trsm_expr, require_all_trsm_expr, TraitTrsmExpr, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_selfadjoint_view, require_selfadjoint_view, require_all_selfadjoint_view,
+                              TraitSelfAdjointView, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_const_selfadjoint_view, require_const_selfadjoint_view,
+                              require_all_const_selfadjoint_view, TraitConstSelfAdjointView, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_symm_expr, require_symm_expr, require_all_symm_expr, TraitSymmExpr, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_syrk_expr, require_syrk_expr, require_all_syrk_expr, TraitSyrkExpr, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_llt_solve_expr, require_llt_solve_expr, require_all_llt_solve_expr, TraitLltSolveExpr,
+                              TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_lu_solve_expr, require_lu_solve_expr, require_all_lu_solve_expr, TraitLuSolveExpr,
+                              TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_llt_view, require_llt_view, require_all_llt_view, TraitLltView, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_lu_view, require_lu_view, require_all_lu_view, TraitLuView, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_device_add_expr, require_device_add_expr, require_all_device_add_expr,
+                              TraitDeviceAddExpr, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_device_scaled_device, require_device_scaled_device, require_all_device_scaled_device,
+                              TraitDeviceScaledDevice, TraitDeviceMatrix);
+EIGEN_GPU_STATIC_ASSERT_TRAIT(is_device_scalar, require_device_scalar, require_all_device_scalar, TraitDeviceScalar,
+                              TraitDeviceMatrix);
+
+#undef EIGEN_GPU_STATIC_ASSERT_TRAIT
+
+// is_device_buffer has no require_all_ variant, so it is checked directly.
+static_assert(gpu::is_device_buffer_v<TraitDeviceBuffer>, "internal::DeviceBuffer is a device buffer");
+static_assert(gpu::is_device_buffer_v<const TraitDeviceBuffer&>, "is_device_buffer_v should decay cv/ref");
+static_assert(!gpu::is_device_buffer_v<TraitDeviceMatrix>, "a DeviceMatrix is not a DeviceBuffer");
+static_assert(std::is_same<gpu::require_device_buffer<TraitDeviceBuffer>, int>::value,
+              "require_device_buffer should compile");
+
+// is_scaled is name-keyed; is_scaled_leaf / is_scaled_gemm compose it with a
+// predicate over Scaled::Inner. None of the three has a require_all_ variant.
+static_assert(gpu::is_scaled_v<TraitScaledMatrix>, "Scaled is a scaled node");
+static_assert(gpu::is_scaled_v<const TraitScaledMatrix&>, "is_scaled_v should decay cv/ref");
+static_assert(!gpu::is_scaled_v<TraitDeviceMatrix>, "a DeviceMatrix is not a scaled node");
+static_assert(std::is_same<gpu::require_scaled<TraitScaledMatrix>, int>::value, "require_scaled should compile");
+
+// Scaled names its operand, which is what the two composed predicates read.
+static_assert(std::is_same<TraitScaledMatrix::Inner, TraitDeviceMatrix>::value, "Scaled exposes its inner type");
+static_assert(std::is_same<gpu::Scaled<const TraitDeviceMatrix&>::Inner, TraitDeviceMatrix>::value,
+              "Scaled::Inner decays the operand");
+
+static_assert(gpu::is_scaled_leaf_v<TraitScaledMatrix>, "Scaled<DeviceMatrix> is a scaled leaf");
+static_assert(gpu::is_scaled_leaf_v<TraitScaledMatrix&&>, "is_scaled_leaf_v should decay refs");
+static_assert(gpu::is_scaled_leaf_v<gpu::Scaled<const TraitDeviceMatrix&>>,
+              "a scaled leaf is still a leaf through Scaled::Inner's decay");
+static_assert(!gpu::is_scaled_leaf_v<gpu::Scaled<TraitAdjointView>>, "Scaled over a view is not a scaled leaf");
+static_assert(!gpu::is_scaled_leaf_v<gpu::Scaled<TraitTransposeView>>, "Scaled over a view is not a scaled leaf");
+static_assert(!gpu::is_scaled_leaf_v<gpu::Scaled<TraitGemmExpr>>, "Scaled over a composite is not a scaled leaf");
+static_assert(!gpu::is_scaled_leaf_v<TraitDeviceMatrix>, "a bare leaf is not a scaled leaf");
+
+static_assert(gpu::is_scaled_gemm_v<gpu::Scaled<TraitGemmExpr>>, "Scaled<GemmExpr> is a scaled product");
+static_assert(!gpu::is_scaled_gemm_v<TraitGemmExpr>, "a bare GemmExpr is not a scaled product");
+static_assert(!gpu::is_scaled_gemm_v<TraitScaledMatrix>, "Scaled<leaf> is not a scaled product");
+static_assert(!gpu::is_scaled_gemm_v<gpu::Scaled<TraitAdjointView>>, "Scaled<view> is not a scaled product");
+
+// The gate short-circuits: a non-Scaled operand must answer false, not fail to
+// compile on the missing ::Inner member.
+static_assert(!gpu::is_scaled_leaf_v<TraitAdjointView>, "a view has no ::Inner and is not a scaled leaf");
+static_assert(!gpu::is_scaled_gemm_v<TraitDeviceScalar>, "a device scalar has no ::Inner and is not a scaled product");
+
+static_assert(gpu::is_gemm_like_v<TraitGemmExpr>, "a GemmExpr is gemm-like");
+static_assert(gpu::is_gemm_like_v<gpu::Scaled<TraitGemmExpr>>, "Scaled<GemmExpr> is gemm-like");
+static_assert(!gpu::is_gemm_like_v<TraitDeviceMatrix>, "a leaf is not gemm-like");
+static_assert(!gpu::is_gemm_like_v<TraitDeviceAddExpr>, "a sum is not gemm-like");
+
+// A factorization handle of either kind satisfies the combined operator gate.
+static_assert(gpu::is_factor_expr_v<TraitLuView>, "LUView is a factorization handle");
+static_assert(gpu::is_factor_expr_v<TraitLltView>, "LLTView is a factorization handle");
+static_assert(!gpu::is_factor_expr_v<TraitDeviceMatrix>, "a leaf is not a factorization handle");
+
+// Query aliases over device_expr_traits (specializations from DeviceExpr.h).
+static_assert(gpu::is_device_expr_v<TraitDeviceMatrix>, "a leaf is a device-expression operand");
+static_assert(gpu::is_device_expr_v<const TraitAdjointView&>, "an adjoint view is a device-expression operand");
+static_assert(gpu::is_device_expr_v<TraitTransposeView&&>, "a transpose view is a device-expression operand");
+static_assert(gpu::is_device_expr_v<TraitScaledMatrix>, "a scaled leaf is a device-expression operand");
+static_assert(!gpu::is_device_expr_v<TraitGemmExpr>, "a GemmExpr is dispatched, not an operand");
+static_assert(std::is_same<gpu::scalar_type_t<const TraitScaledMatrix&>, double>::value,
+              "scalar_type_t decays cv/ref and folds the operand scalar");
+static_assert(std::is_same<gpu::scalar_type_t<TraitAdjointView>, double>::value, "scalar_type_t reads the view scalar");
+static_assert(gpu::trans_op<TraitDeviceMatrix> == gpu::GpuOp::NoTrans, "a leaf is NoTrans");
+static_assert(gpu::trans_op<TraitAdjointView> == gpu::GpuOp::ConjTrans, "an adjoint view is ConjTrans");
+static_assert(gpu::trans_op<TraitTransposeView> == gpu::GpuOp::Trans, "a transpose view is Trans");
+static_assert(std::is_same<gpu::require_all_device_expr<TraitDeviceMatrix, const TraitAdjointView&, TraitScaledMatrix>,
+                           int>::value,
+              "require_all_device_expr should compile for device-expression operands");
+}  // namespace
+
+EIGEN_DECLARE_TEST(device_traits) {}