This is an automated email from the ASF dual-hosted git repository.

junrushao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git


The following commit(s) were added to refs/heads/main by this push:
     new 5569e44  [Minor] Improve tuple functionality (#219)
5569e44 is described below

commit 5569e449b654923009980b67a2af9aed323a6dad
Author: DarkSharpness <[email protected]>
AuthorDate: Wed Nov 5 05:46:33 2025 +0800

    [Minor] Improve tuple functionality (#219)
    
    Enhance tvm::ffi::Tuple so that it will support structural binding,
    which can greatly improve C++ readability (hopefully).
---
 include/tvm/ffi/container/tuple.h | 72 ++++++++++++++++++++++++++++++++++++++-
 tests/cpp/test_tuple.cc           | 67 ++++++++++++++++++++++++++++++++++++
 2 files changed, 138 insertions(+), 1 deletion(-)

diff --git a/include/tvm/ffi/container/tuple.h 
b/include/tvm/ffi/container/tuple.h
index dbcf7d0..e5eb3ca 100644
--- a/include/tvm/ffi/container/tuple.h
+++ b/include/tvm/ffi/container/tuple.h
@@ -26,8 +26,10 @@
 
 #include <tvm/ffi/container/array.h>
 
+#include <cstddef>
 #include <string>
 #include <tuple>
+#include <type_traits>
 #include <utility>
 
 namespace tvm {
@@ -141,13 +143,32 @@ class Tuple : public ObjectRef {
    * \note We use stl style since get usually is like a getter.
    */
   template <size_t I>
-  auto get() const {
+  auto get() const& {
     static_assert(I < sizeof...(Types), "Tuple index out of bounds");
     using ReturnType = std::tuple_element_t<I, std::tuple<Types...>>;
     const Any* ptr = GetArrayObj()->begin() + I;
     return details::AnyUnsafe::CopyFromAnyViewAfterCheck<ReturnType>(*ptr);
   }
 
+  /*!
+   * \brief Move out I-th element of the tuple
+   *
+   * \tparam I The index of the element to get
+   * \return The I-th element of the tuple
+   * \note We use stl style since get usually is like a getter.
+   */
+  template <size_t I>
+  auto get() && {
+    if (!this->unique()) {
+      // fallback to const& version if not unique
+      return std::as_const(*this).template get<I>();
+    }
+    static_assert(I < sizeof...(Types), "Tuple index out of bounds");
+    using ReturnType = std::tuple_element_t<I, std::tuple<Types...>>;
+    Any* ptr = GetArrayObj()->MutableBegin() + I;
+    return 
details::AnyUnsafe::MoveFromAnyAfterCheck<ReturnType>(std::move(*ptr));
+  }
+
   /*!
    * \brief Set I-th element of the tuple
    *
@@ -320,6 +341,55 @@ template <typename... T, typename... U>
 inline constexpr bool type_contains_v<Tuple<T...>, Tuple<U...>> = 
(type_contains_v<T, U> && ...);
 }  // namespace details
 
+/// \cond Doxygen_Suppress
+
+/// NOTE: ADL friendly get functions
+/// Example usage: { using std::get; get<0>(t); }
+/// ADL will find the right get function
+
+/**
+ * \brief get I-th element of the tuple
+ * \tparam I The index of the element to get
+ * \param t The tuple
+ * \return The I-th element of the tuple
+ */
+template <std::size_t I, typename... Types>
+inline constexpr auto get(const Tuple<Types...>& t)
+    -> std::tuple_element_t<I, std::tuple<Types...>> {
+  return t.template get<I>();
+}
+
+/**
+ * \brief get I-th element of the tuple
+ * \tparam I The index of the element to get
+ * \param t The tuple (rvalue)
+ * \return The I-th element of the tuple
+ */
+template <std::size_t I, typename... Types>
+inline constexpr auto get(Tuple<Types...>&& t) -> std::tuple_element_t<I, 
std::tuple<Types...>> {
+  return std::move(t).template get<I>();
+}
+
+/// NOTE: C++17 deduction guide
+template <typename... UTypes>
+Tuple(UTypes&&...) -> 
Tuple<std::remove_cv_t<std::remove_reference_t<UTypes>>...>;
+
+/// \endcond
+
 }  // namespace ffi
 }  // namespace tvm
+
+namespace std {
+
+template <typename... Types>
+struct tuple_size<::tvm::ffi::Tuple<Types...>>
+    : public std::integral_constant<size_t, sizeof...(Types)> {};
+
+template <size_t I, typename... Types>
+struct tuple_element<I, ::tvm::ffi::Tuple<Types...>> {
+  using type = std::tuple_element_t<I, std::tuple<Types...>>;
+};
+
+}  // namespace std
+
 #endif  // TVM_FFI_CONTAINER_TUPLE_H_
diff --git a/tests/cpp/test_tuple.cc b/tests/cpp/test_tuple.cc
index 0347a70..89f79c2 100644
--- a/tests/cpp/test_tuple.cc
+++ b/tests/cpp/test_tuple.cc
@@ -20,6 +20,8 @@
 #include <tvm/ffi/container/tuple.h>
 #include <tvm/ffi/function.h>
 
+#include <utility>
+
 #include "./testing_object.h"
 
 namespace {
@@ -168,4 +170,69 @@ TEST(Tuple, ArrayIterForwardSingleElem) {
   EXPECT_EQ(vec0[2].get<0>()->value, 2);
 }
 
+TEST(Tuple, CPPFeatures) {
+  {
+    // deduction guide
+    auto t = Tuple{1, 2.0f, String{"hello"}};
+    // structural binding (lvalue)
+    auto [a, b, c] = t;
+    EXPECT_EQ(a, 1);
+    EXPECT_EQ(b, 2.0f);
+    EXPECT_EQ(c, "hello");
+  }
+
+  {
+    // ADL-friendly get will find the right get function
+    auto p = Tuple{Array<int>{0}};
+    EXPECT_EQ(p.use_count(), 1);
+    // p<0> and q each hold a reference
+    const auto q = get<0>(p);
+    EXPECT_EQ(q.use_count(), 2);
+  }
+
+  {
+    auto p = Tuple{Array<int>{0}};
+    EXPECT_EQ(p.use_count(), 1);
+    // structured binding (rvalue)
+    // move out the only ownership
+    auto [q] = std::move(p);
+    EXPECT_EQ(q.use_count(), 1);
+  }
+
+  {
+    // ADL-friendly get with move semantics
+    auto p = Tuple{Array<int>{0}};
+
+    // normal copy get, won't move out anything
+    {
+      auto& q = p;
+      EXPECT_EQ(q.use_count(), 1);
+      const auto _ = get<0>(q);
+    }
+    EXPECT_TRUE(get<0>(p).defined());
+
+    // ref-count of q > 1, so the array obj is not moved out
+    {
+      auto q = p;
+      EXPECT_EQ(q.use_count(), 2);
+      const auto _ = get<0>(std::move(q));
+    }
+    EXPECT_TRUE(get<0>(p).defined());
+
+    // ref-count of q = 1, so the array obj is moved out
+    {
+      auto& q = p;
+      EXPECT_EQ(q.use_count(), 1);
+      // NOTE: we do not use structured binding here,
+      // as it creates a temporary value that will move out the q (i.e. p),
+      // making p in an not defined state (p.data_ == 0).
+      // Our get resolution accepts rvalue reference,
+      // which keeps p in a defined state for testing purposes.
+      // DO NOT rely on this behavior in real code.
+      const auto _ = get<0>(std::move(q));
+    }
+    EXPECT_FALSE(get<0>(p).defined());
+  }
+}
+
 }  // namespace

Reply via email to