[libc++] Re-implement LWG2770 again * 2 (#132598)

1013fe3c0cfd7582e94ef2d4bfd79da7ea1a1289 used to implement LWG2770, but
cb0d4df97490ec2d2b1cdf7574d26b1bc4063599 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.
This commit is contained in:
A. Jiang 2025-03-26 07:38:02 +08:00 committed by GitHub
parent 134cb8877e
commit af267993a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 102 additions and 15 deletions

View File

@ -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|","",""

1 Issue # Issue Name Meeting Status First released version Notes
252 `LWG2760 <https://wg21.link/LWG2760>`__ non-const basic_string::data should not invalidate iterators 2016-11 (Issaquah) |Complete|
253 `LWG2765 <https://wg21.link/LWG2765>`__ Did LWG 1123 go too far? 2016-11 (Issaquah) |Complete|
254 `LWG2767 <https://wg21.link/LWG2767>`__ not_fn call_wrapper can form invalid types 2016-11 (Issaquah) |Complete|
255 `LWG2770 <https://wg21.link/LWG2770>`__ ``tuple_size<const T>`` specialization is not SFINAE compatible and breaks decomposition declarations 2016-11 (Issaquah) |Complete| 21
256 `LWG2771 <https://wg21.link/LWG2771>`__ Broken Effects of some basic_string::compare functions in terms of basic_string_view 2016-11 (Issaquah) |Complete|
257 `LWG2773 <https://wg21.link/LWG2773>`__ Making std::ignore constexpr 2016-11 (Issaquah) |Complete|
258 `LWG2777 <https://wg21.link/LWG2777>`__ basic_string_view::copy should use char_traits::copy 2016-11 (Issaquah) |Complete|

View File

@ -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

View File

@ -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>(), "");
}

View File

@ -23,6 +23,8 @@
struct Dummy1 {};
struct Dummy2 {};
struct Dummy3 {};
struct Dummy4 {};
struct Dummy5 {};
template <>
struct std::tuple_size<Dummy1> {
@ -39,6 +41,16 @@ public:
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 {{no member named 'value'}}
(void)std::tuple_size<const Dummy3>::value; // expected-note {{here}}
// 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 {{invalid use of non-static data member 'value'}}
(void)std::tuple_size<const Dummy5>::value; // expected-note {{here}}
}
}

View File

@ -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 @@ public:
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
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;
}