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++] Re-implement LWG2770 again * 2 #132598

Merged
merged 1 commit into from
Mar 25, 2025

Conversation

frederick-vs-ja
Copy link
Contributor

1013fe3 used to implement LWG2770, but cb0d4df made LWG2770 unimplemented again because of CWG2386.

This patch re-implements LWG2770, while keeping the libc++-specific implementation strategy (which is controversial as noted in LWG4040).

Drive-by:

  • Make the test coverage for the controversial part noted in LWG4040 libc++-only.
  • Add the previously missed entry for LWG2770 to the documentation.

@frederick-vs-ja frederick-vs-ja requested a review from a team as a code owner March 23, 2025 08:02
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Mar 23, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 23, 2025

@llvm/pr-subscribers-libcxx

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

Changes

1013fe3 used to implement LWG2770, but cb0d4df made LWG2770 unimplemented again because of CWG2386.

This patch re-implements LWG2770, while keeping the libc++-specific implementation strategy (which is controversial as noted in LWG4040).

Drive-by:

  • Make the test coverage for the controversial part noted in LWG4040 libc++-only.
  • Add the previously missed entry for LWG2770 to the documentation.

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

5 Files Affected:

  • (modified) libcxx/docs/Status/Cxx17Issues.csv (+1)
  • (modified) libcxx/include/__tuple/tuple_size.h (+5-8)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.pass.cpp (+5-3)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.verify.cpp (+27-3)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_structured_bindings.pass.cpp (+64-1)
diff --git a/libcxx/docs/Status/Cxx17Issues.csv b/libcxx/docs/Status/Cxx17Issues.csv
index d09b37f1d3900..3ca3e22845cfd 100644
--- a/libcxx/docs/Status/Cxx17Issues.csv
+++ b/libcxx/docs/Status/Cxx17Issues.csv
@@ -252,6 +252,7 @@
 "`LWG2760 <https://wg21.link/LWG2760>`__","non-const basic_string::data should not invalidate iterators","2016-11 (Issaquah)","|Complete|","",""
 "`LWG2765 <https://wg21.link/LWG2765>`__","Did LWG 1123 go too far?","2016-11 (Issaquah)","|Complete|","",""
 "`LWG2767 <https://wg21.link/LWG2767>`__","not_fn call_wrapper can form invalid types","2016-11 (Issaquah)","|Complete|","",""
+"`LWG2770 <https://wg21.link/LWG2770>`__","``tuple_size<const T>`` specialization is not SFINAE compatible and breaks decomposition declarations","2016-11 (Issaquah)","|Complete|","21",""
 "`LWG2771 <https://wg21.link/LWG2771>`__","Broken Effects of some basic_string::compare functions in terms of basic_string_view","2016-11 (Issaquah)","|Complete|","",""
 "`LWG2773 <https://wg21.link/LWG2773>`__","Making std::ignore constexpr","2016-11 (Issaquah)","|Complete|","",""
 "`LWG2777 <https://wg21.link/LWG2777>`__","basic_string_view::copy should use char_traits::copy","2016-11 (Issaquah)","|Complete|","",""
diff --git a/libcxx/include/__tuple/tuple_size.h b/libcxx/include/__tuple/tuple_size.h
index 27d57eb56ba6b..035ffa3655bc1 100644
--- a/libcxx/include/__tuple/tuple_size.h
+++ b/libcxx/include/__tuple/tuple_size.h
@@ -32,20 +32,17 @@ template <class _Tp, class...>
 using __enable_if_tuple_size_imp _LIBCPP_NODEBUG = _Tp;
 
 template <class _Tp>
-struct _LIBCPP_TEMPLATE_VIS tuple_size<__enable_if_tuple_size_imp< const _Tp,
-                                                                   __enable_if_t<!is_volatile<_Tp>::value>,
-                                                                   integral_constant<size_t, sizeof(tuple_size<_Tp>)>>>
+struct _LIBCPP_TEMPLATE_VIS tuple_size<
+    __enable_if_tuple_size_imp<const _Tp, __enable_if_t<!is_volatile<_Tp>::value>, decltype(tuple_size<_Tp>::value)>>
     : public integral_constant<size_t, tuple_size<_Tp>::value> {};
 
 template <class _Tp>
-struct _LIBCPP_TEMPLATE_VIS tuple_size<__enable_if_tuple_size_imp< volatile _Tp,
-                                                                   __enable_if_t<!is_const<_Tp>::value>,
-                                                                   integral_constant<size_t, sizeof(tuple_size<_Tp>)>>>
+struct _LIBCPP_TEMPLATE_VIS tuple_size<
+    __enable_if_tuple_size_imp<volatile _Tp, __enable_if_t<!is_const<_Tp>::value>, decltype(tuple_size<_Tp>::value)>>
     : public integral_constant<size_t, tuple_size<_Tp>::value> {};
 
 template <class _Tp>
-struct _LIBCPP_TEMPLATE_VIS
-tuple_size<__enable_if_tuple_size_imp<const volatile _Tp, integral_constant<size_t, sizeof(tuple_size<_Tp>)>>>
+struct _LIBCPP_TEMPLATE_VIS tuple_size<__enable_if_tuple_size_imp<const volatile _Tp, decltype(tuple_size<_Tp>::value)>>
     : public integral_constant<size_t, tuple_size<_Tp>::value> {};
 
 #else
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.pass.cpp
index a2cb2e989a571..bef4486d586b3 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.pass.cpp
@@ -45,9 +45,11 @@ void test_complete() {
 template <class T>
 void test_incomplete() {
   static_assert(!is_complete<T>(), "");
-  static_assert(!is_complete<const T>(), "");
-  static_assert(!is_complete<volatile T>(), "");
-  static_assert(!is_complete<const volatile T>(), "");
+  // https://cplusplus.github.io/LWG/issue4040
+  // It is controversial whether these specializations are incomplete.
+  LIBCPP_STATIC_ASSERT(!is_complete<const T>(), "");
+  LIBCPP_STATIC_ASSERT(!is_complete<volatile T>(), "");
+  LIBCPP_STATIC_ASSERT(!is_complete<const volatile T>(), "");
 }
 
 
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.verify.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.verify.cpp
index 532ccd730ffc1..cc6af820c930b 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.verify.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_incomplete.verify.cpp
@@ -23,6 +23,8 @@
 struct Dummy1 {};
 struct Dummy2 {};
 struct Dummy3 {};
+struct Dummy4 {};
+struct Dummy5 {};
 
 template <>
 struct std::tuple_size<Dummy1> {
@@ -39,6 +41,16 @@ struct std::tuple_size<Dummy2> {
 template <>
 struct std::tuple_size<Dummy3> {};
 
+template <>
+struct std::tuple_size<Dummy4> {
+  void value();
+};
+
+template <>
+struct std::tuple_size<Dummy5> {
+  size_t value;
+};
+
 void f() {
   // Test that tuple_size<const T> is not incomplete when tuple_size<T>::value
   // is well-formed but not a constant expression.
@@ -53,9 +65,21 @@ void f() {
     (void)std::tuple_size<const Dummy2>::value; // expected-note {{here}}
   }
   // Test that tuple_size<const T> generates an error when tuple_size<T> is
-  // complete but ::value isn't a constant expression convertible to size_t.
+  // complete but has no ::value member.
+  {
+    // expected-error@*:* 1 {{implicit instantiation of undefined template}}
+    (void)std::tuple_size<const Dummy3>::value;
+  }
+  // Test that tuple_size<const T> generates an error when tuple_size<T> has
+  // the ::value member but tuple_size<T>::value is ill-formed.
+  {
+    // expected-error@*:* 1 {{implicit instantiation of undefined template}}
+    (void)std::tuple_size<const Dummy4>::value;
+  }
+  // Test that tuple_size<const T> generates an error when tuple_size<T> has
+  // the ::value member which is non-static.
   {
-    // expected-error@*:* 1 {{no member named 'value'}}
-    (void)std::tuple_size<const Dummy3>::value; // expected-note {{here}}
+    // expected-error@*:* 1 {{invalid use of non-static data member 'value'}}
+    (void)std::tuple_size<const Dummy5>::value; // expected-note {{here}}
   }
 }
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_structured_bindings.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_structured_bindings.pass.cpp
index 2324da3ad5f3c..9016421da15eb 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_structured_bindings.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.helper/tuple_size_structured_bindings.pass.cpp
@@ -109,6 +109,36 @@ void test_decomp_array() {
   }
 }
 
+struct TestLWG2770 {
+  int n;
+};
+
+template <>
+struct std::tuple_size<TestLWG2770> {};
+
+void test_lwg_2770() {
+  {
+    auto [n] = TestLWG2770{42};
+    assert(n == 42);
+  }
+  {
+    const auto [n] = TestLWG2770{42};
+    assert(n == 42);
+  }
+  {
+    TestLWG2770 s{42};
+    auto& [n] = s;
+    assert(n == 42);
+    assert(&n == &s.n);
+  }
+  {
+    const TestLWG2770 s{42};
+    auto& [n] = s;
+    assert(n == 42);
+    assert(&n == &s.n);
+  }
+}
+
 struct Test {
   int x;
 };
@@ -136,16 +166,49 @@ struct std::tuple_size<Test> {
 void test_after_tuple_size_specialization() {
   Test const t{99};
   auto& [p] = t;
-  assert(p == -1);
+  // https://cplusplus.github.io/LWG/issue4040
+  // It is controversial whether std::tuple_size<const Test> is instantiated here or before.
+  (void)p;
+  LIBCPP_ASSERT(p == -1);
+}
+
+#if TEST_STD_VER >= 26
+struct InvalidWhenNoCv1 {};
+
+template <>
+struct std::tuple_size<InvalidWhenNoCv1> {};
+
+struct InvalidWhenNoCv2 {};
+
+template <>
+struct std::tuple_size<InvalidWhenNoCv2> {
+  void value();
+};
+
+template <class = void>
+void test_decomp_as_empty_pack() {
+  {
+    const auto [... pack] = InvalidWhenNoCv1{};
+    static_assert(sizeof...(pack) == 0);
+  }
+  {
+    const auto [... pack] = InvalidWhenNoCv2{};
+    static_assert(sizeof...(pack) == 0);
+  }
 }
+#endif
 
 int main(int, char**) {
   test_decomp_user_type();
   test_decomp_tuple();
   test_decomp_pair();
   test_decomp_array();
+  test_lwg_2770();
   test_before_tuple_size_specialization();
   test_after_tuple_size_specialization();
+#if TEST_STD_VER >= 26
+  test_decomp_as_empty_pack();
+#endif
 
   return 0;
 }

1013fe3 used to implement LWG2770, but
cb0d4df made LWG2770 unimplemented
again because of CWG2386.

This patch re-implements LWG2770, while keeping the libc++-specific
implementation strategy (which is controversial as noted in LWG4040).

Drive-by:
- Make the test coverage for the controversial part noted in LWG4040
libc++-only.
- Add the previously missed entry for LWG2770 to the documentation.
LIBCPP_ASSERT(p == -1);
}

#if TEST_STD_VER >= 26 && __cpp_structured_bindings >= 202411L
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
#if TEST_STD_VER >= 26 && __cpp_structured_bindings >= 202411L
#if TEST_STD_VER >= 26

We normally just assume that TEST_STD_VER >= 26 implies that the language feature is available. Is there a reason to stray from that here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

GCC hasn't implemented this feature yet, while I think it's worth testing empty structured bindings.

@frederick-vs-ja frederick-vs-ja merged commit af26799 into llvm:main Mar 25, 2025
82 checks passed
@frederick-vs-ja frederick-vs-ja deleted the lwg-2770 branch March 25, 2025 23:38
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.

3 participants