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
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libcxx/docs/Status/Cxx17Issues.csv
Original file line number Diff line number Diff line change
@@ -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|","",""
13 changes: 5 additions & 8 deletions libcxx/include/__tuple/tuple_size.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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>(), "");
}


Original file line number Diff line number Diff line change
@@ -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}}
}
}
Original file line number Diff line number Diff line change
@@ -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 && __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.

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 && __cpp_structured_bindings >= 202411L
test_decomp_as_empty_pack();
#endif

return 0;
}