Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++] Fix tuple_cat for element with unconstrained constructor #122433

Merged
merged 3 commits into from
Jan 14, 2025

Conversation

frederick-vs-ja
Copy link
Contributor

Currently, when the result type is 1-tuple, tuple_cat possibly tests an undesired constructor of the element, due to conversion from the reference tuple to the result type. If the element type has an unconstrained constructor template, there can be extraneous hard error which shouldn't happen.

This patch introduces a helper function template __tuple_cat_select_element_wise to select the element-wise constructor template of tuple, which can avoid such error.

Fixes #41034.

Currently, when the result type is 1-`tuple`, `tuple_cat` possibly tests an undesired constructor of the element, due to conversion from the reference tuple to the result type. If the element type has an unconstrained constructor template, there can be extraneous hard error which shouldn't happen.

This patch introduces a helper function template `__tuple_cat_select_element_wise` to select the element-wise constructor template of `tuple`, which can avoid such error.
@frederick-vs-ja frederick-vs-ja requested a review from a team as a code owner January 10, 2025 09:26
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jan 10, 2025
@llvmbot
Copy link
Member

llvmbot commented Jan 10, 2025

@llvm/pr-subscribers-libcxx

Author: A. Jiang (frederick-vs-ja)

Changes

Currently, when the result type is 1-tuple, tuple_cat possibly tests an undesired constructor of the element, due to conversion from the reference tuple to the result type. If the element type has an unconstrained constructor template, there can be extraneous hard error which shouldn't happen.

This patch introduces a helper function template __tuple_cat_select_element_wise to select the element-wise constructor template of tuple, which can avoid such error.

Fixes #41034.


Full diff: https://github.com/llvm/llvm-project/pull/122433.diff

2 Files Affected:

  • (modified) libcxx/include/tuple (+16-3)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.creation/tuple_cat.pass.cpp (+73-1)
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index aca14ba408d314..9728c8c3b99314 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -1338,12 +1338,25 @@ struct __tuple_cat<tuple<_Types...>, __tuple_indices<_I0...>, __tuple_indices<_J
   }
 };
 
+template <class _TupleDst, class _TupleSrc, size_t... _Indices>
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _TupleDst
+__tuple_cat_select_element_wise(_TupleSrc&& __src, __tuple_indices<_Indices...>) {
+  static_assert(tuple_size<_TupleDst>::value == tuple_size<_TupleSrc>::value,
+                "misuse of __tuple_cat_select_element_wise with tuples of different sizes");
+  return _TupleDst(std::get<_Indices>(std::forward<_TupleSrc>(__src))...);
+}
+
 template <class _Tuple0, class... _Tuples>
 inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 typename __tuple_cat_return<_Tuple0, _Tuples...>::type
 tuple_cat(_Tuple0&& __t0, _Tuples&&... __tpls) {
-  using _T0 _LIBCPP_NODEBUG = __libcpp_remove_reference_t<_Tuple0>;
-  return __tuple_cat<tuple<>, __tuple_indices<>, typename __make_tuple_indices<tuple_size<_T0>::value>::type>()(
-      tuple<>(), std::forward<_Tuple0>(__t0), std::forward<_Tuples>(__tpls)...);
+  using _T0 _LIBCPP_NODEBUG          = __libcpp_remove_reference_t<_Tuple0>;
+  using _TRet _LIBCPP_NODEBUG        = typename __tuple_cat_return<_Tuple0, _Tuples...>::type;
+  using _T0Indices _LIBCPP_NODEBUG   = typename __make_tuple_indices<tuple_size<_T0>::value>::type;
+  using _TRetIndices _LIBCPP_NODEBUG = typename __make_tuple_indices<tuple_size<_TRet>::value>::type;
+  return std::__tuple_cat_select_element_wise<_TRet>(
+      __tuple_cat<tuple<>, __tuple_indices<>, _T0Indice>()(
+          tuple<>(), std::forward<_Tuple0>(__t0), std::forward<_Tuples>(__tpls)...),
+      _TRetIndice());
 }
 
 template <class... _Tp, class _Alloc>
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.creation/tuple_cat.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.creation/tuple_cat.pass.cpp
index 00c9d27ccc6d05..3f978f99c2cb47 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.creation/tuple_cat.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.creation/tuple_cat.pass.cpp
@@ -31,6 +31,70 @@ template<typename ...Ts>
 void forward_as_tuple(Ts...) = delete;
 }
 
+// https://github.com/llvm/llvm-project/issues/41034
+struct Unconstrained {
+  int data;
+  template <typename Arg>
+  TEST_CONSTEXPR_CXX14 Unconstrained(Arg arg) : data(arg) {}
+};
+
+TEST_CONSTEXPR_CXX14 std::tuple<Unconstrained> test_cat_unary_lvalue() {
+  auto tup = std::tuple<Unconstrained>(Unconstrained(5));
+  return std::tuple_cat(tup);
+}
+
+TEST_CONSTEXPR_CXX14 std::tuple<Unconstrained> test_cat_unary_rvalue() {
+  return std::tuple_cat(std::tuple<Unconstrained>(Unconstrained(6)));
+}
+
+TEST_CONSTEXPR_CXX14 std::tuple<Unconstrained> test_cat_unary_and_nullary() {
+  return std::tuple_cat(std::tuple<Unconstrained>(Unconstrained(7)), std::tuple<>());
+}
+
+#if TEST_STD_VER >= 17
+constexpr auto test_cat_unary_lvalue_ctad() {
+  auto tup = std::tuple(Unconstrained(8));
+  return std::tuple_cat(tup);
+}
+
+constexpr auto test_cat_unary_rvalue_ctad() { return std::tuple_cat(std::tuple(Unconstrained(9))); }
+
+constexpr auto test_cat_unary_and_nullary_ctad() { return std::tuple_cat(std::tuple(Unconstrained(10)), std::tuple()); }
+#endif
+
+TEST_CONSTEXPR_CXX14 bool test_tuple_cat_with_unconstrained_constructor() {
+  {
+    auto tup = test_cat_unary_lvalue();
+    assert(std::get<0>(tup).data == 5);
+  }
+  {
+    auto tup = test_cat_unary_rvalue();
+    assert(std::get<0>(tup).data == 6);
+  }
+  {
+    auto tup = test_cat_unary_and_nullary();
+    assert(std::get<0>(tup).data == 7);
+  }
+#if TEST_STD_VER >= 17
+  {
+    auto tup = test_cat_unary_lvalue_ctad();
+    ASSERT_SAME_TYPE(decltype(tup), std::tuple<Unconstrained>);
+    assert(std::get<0>(tup).data == 8);
+  }
+  {
+    auto tup = test_cat_unary_rvalue_ctad();
+    ASSERT_SAME_TYPE(decltype(tup), std::tuple<Unconstrained>);
+    assert(std::get<0>(tup).data == 9);
+  }
+  {
+    auto tup = test_cat_unary_and_nullary_ctad();
+    ASSERT_SAME_TYPE(decltype(tup), std::tuple<Unconstrained>);
+    assert(std::get<0>(tup).data == 10);
+  }
+#endif
+  return true;
+}
+
 int main(int, char**)
 {
     {
@@ -270,5 +334,13 @@ int main(int, char**)
         assert(std::get<0>(t).i == 1);
         assert(std::get<0>(t2).i == 1);
     }
-  return 0;
+    // See https://github.com/llvm/llvm-project/issues/41034
+    {
+      test_tuple_cat_with_unconstrained_constructor();
+#if TEST_STD_VER >= 14
+      static_assert(test_tuple_cat_with_unconstrained_constructor(), "");
+#endif
+    }
+
+    return 0;
 }

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with minor testing request.

@ldionne ldionne merged commit e03c435 into llvm:main Jan 14, 2025
76 checks passed
@frederick-vs-ja frederick-vs-ja deleted the cat-tuple-unconstrained branch January 14, 2025 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Issue using std::tuple_cat to construct a single-element tuple containing a type with a templated constructor
3 participants