[libc++] Remove non-conforming __bit_reference::operator& (#188714)

The overloaded `operator&` caused non-conforming behavior when
- using `operator==` to compare "addresses" of proxy reference objects,
and
- relying on the exact type of `&ref`.

No deprecation warning is added, becaue it should be portable to write
`&ref` where `ref` is a proxy reference variable, and this patch just
corrects the behavior.

`__bit_const_reference::operator&` is kept, because when one defines
`_LIBCPP_ABI_BITSET_VECTOR_BOOL_CONST_SUBSCRIPT_RETURN_BOOL` to make the
libc++ implementation strategy conforming, the `operator&` will never be
exposed to users.
This commit is contained in:
A. Jiang
2026-03-31 13:45:20 +08:00
committed by GitHub
parent d3224dc5da
commit 2e02135cec
4 changed files with 213 additions and 3 deletions

View File

@@ -58,6 +58,9 @@ Deprecations and Removals
- In ``__wrap_iter`` (iterator wrapper type for ``array``, ``span``, ``string``, ``string_view`` and ``vector``),
the ``base()`` method and ``iterator_type`` member type have been removed as they are non-standard.
- In ``__bit_reference`` (the proxy ``reference`` type of ``bitset`` and ``vector<bool>``), the overloaded ``operator&``
is removed as it is non-standard and causes non-conforming behavior.
Potentially breaking changes
----------------------------

View File

@@ -165,9 +165,6 @@ public:
}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void flip() _NOEXCEPT { *__seg_ ^= __mask_; }
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __bit_iterator<_Cp, false> operator&() const _NOEXCEPT {
return __bit_iterator<_Cp, false>(__seg_, static_cast<unsigned>(std::__countr_zero(__mask_)));
}
private:
_LIBCPP_HIDE_FROM_ABI

View File

@@ -0,0 +1,102 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// <vector>
// template<class Allocator>
// class vector<bool, Allocator>::reference;
// Verify that vector<bool, Allocator>::reference has no overloaded operator&.
// XFAIL: FROZEN-CXX03-HEADERS-FIXME
#include <cassert>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "test_macros.h"
#include "min_allocator.h"
namespace test_overloads {
struct dummy {};
void operator&(dummy);
template <class, class = void>
struct has_nonmember_operator_address_of : std::false_type {};
template <class T>
struct has_nonmember_operator_address_of<T, decltype((void)operator&(std::declval<T>()))> : std::true_type {};
template <class, class = void>
struct has_member_operator_address_of : std::false_type {};
template <class T>
struct has_member_operator_address_of<T, decltype((void)std::declval<T>().operator&())> : std::true_type {};
} // namespace test_overloads
template <class, class = void>
struct can_take_address : std::false_type {};
template <class T>
struct can_take_address<T, decltype((void)&std::declval<T>())> : std::true_type {};
template <class, class = void>
struct has_builtin_operator_address_of : std::false_type {};
template <class T>
struct has_builtin_operator_address_of<T, decltype((void)&std::declval<T>())>
: std::integral_constant<bool,
!test_overloads::has_nonmember_operator_address_of<T>::value &&
!test_overloads::has_member_operator_address_of<T>::value> {};
template <class Ref>
TEST_CONSTEXPR_CXX20 void test_proxy_references(Ref& r1, Ref& r2) {
static_assert(can_take_address<Ref&>::value, "");
static_assert(can_take_address<const Ref&>::value, "");
static_assert(!can_take_address<Ref>::value, "");
static_assert(!can_take_address<const Ref>::value, "");
static_assert(has_builtin_operator_address_of<Ref&>::value, "");
static_assert(has_builtin_operator_address_of<const Ref&>::value, "");
static_assert(!has_builtin_operator_address_of<Ref>::value, "");
static_assert(!has_builtin_operator_address_of<const Ref>::value, "");
static_assert(std::is_same<decltype(&r1), Ref*>::value, "");
assert(std::addressof(r1) == &r1);
assert(std::addressof(r2) == &r2);
assert((std::addressof(r1) == std::addressof(r2)) == (&r1 == &r2));
}
template <class VB>
TEST_CONSTEXPR_CXX20 void test() {
VB vec(1);
typename VB::reference r1 = vec[0];
typename VB::reference r2 = vec[0];
test_proxy_references(r1, r2);
assert(&r1 != &r2);
}
TEST_CONSTEXPR_CXX20 bool test() {
test<std::vector<bool> >();
#if TEST_STD_VER >= 11
test<std::vector<bool, min_allocator<bool>>>();
#endif
return true;
}
int main(int, char**) {
test();
#if TEST_STD_VER >= 20
static_assert(test());
#endif
return 0;
}

View File

@@ -0,0 +1,108 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// <bitset>
// template<size_t N>
// class bitset<N>::reference;
// Verify that bitset<N>::reference has no overloaded operator&.
// XFAIL: FROZEN-CXX03-HEADERS-FIXME
#include <cassert>
#include <bitset>
#include <memory>
#include <type_traits>
#include <utility>
#include "test_macros.h"
namespace test_overloads {
struct dummy {};
void operator&(dummy);
template <class, class = void>
struct has_nonmember_operator_address_of : std::false_type {};
template <class T>
struct has_nonmember_operator_address_of<T, decltype((void)operator&(std::declval<T>()))> : std::true_type {};
template <class, class = void>
struct has_member_operator_address_of : std::false_type {};
template <class T>
struct has_member_operator_address_of<T, decltype((void)std::declval<T>().operator&())> : std::true_type {};
} // namespace test_overloads
template <class, class = void>
struct can_take_address : std::false_type {};
template <class T>
struct can_take_address<T, decltype((void)&std::declval<T>())> : std::true_type {};
template <class, class = void>
struct has_builtin_operator_address_of : std::false_type {};
template <class T>
struct has_builtin_operator_address_of<T, decltype((void)&std::declval<T>())>
: std::integral_constant<bool,
!test_overloads::has_nonmember_operator_address_of<T>::value &&
!test_overloads::has_member_operator_address_of<T>::value> {};
template <class Ref>
TEST_CONSTEXPR_CXX23 void test_proxy_references(Ref& r1, Ref& r2) {
static_assert(can_take_address<Ref&>::value, "");
static_assert(can_take_address<const Ref&>::value, "");
static_assert(!can_take_address<Ref>::value, "");
static_assert(!can_take_address<const Ref>::value, "");
static_assert(has_builtin_operator_address_of<Ref&>::value, "");
static_assert(has_builtin_operator_address_of<const Ref&>::value, "");
static_assert(!has_builtin_operator_address_of<Ref>::value, "");
static_assert(!has_builtin_operator_address_of<const Ref>::value, "");
static_assert(std::is_same<decltype(&r1), Ref*>::value, "");
assert(std::addressof(r1) == &r1);
assert(std::addressof(r2) == &r2);
assert((std::addressof(r1) == std::addressof(r2)) == (&r1 == &r2));
}
template <class Bitset>
TEST_CONSTEXPR_CXX23 void test() {
Bitset bs;
typename Bitset::reference r1 = bs[0];
typename Bitset::reference r2 = bs[0];
test_proxy_references(r1, r2);
assert(&r1 != &r2);
}
TEST_CONSTEXPR_CXX23 bool test() {
test<std::bitset<1> >();
test<std::bitset<8> >();
test<std::bitset<12> >();
test<std::bitset<16> >();
test<std::bitset<24> >();
test<std::bitset<32> >();
test<std::bitset<48> >();
test<std::bitset<64> >();
test<std::bitset<96> >();
test<std::bitset<128> >();
test<std::bitset<192> >();
return true;
}
int main(int, char**) {
test();
#if TEST_STD_VER >= 23
static_assert(test());
#endif
return 0;
}