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