Skip to content

Commit 14ec474

Browse files
authored
[libc++] Makes `unique_ptr operator*() noexcept. (#98047)
This implements - LWG2762 unique_ptr operator*() should be noexcept. Differential Revision: https://reviews.llvm.org/D128214
1 parent a41a4ac commit 14ec474

File tree

12 files changed

+97
-65
lines changed

12 files changed

+97
-65
lines changed

libcxx/docs/Status/Cxx23Issues.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
"","","","","",""
100100
`2191 <https://wg21.link/LWG2191>`__,"Incorrect specification of ``match_results(match_results&&)``","October 2021","|Nothing To Do|",""
101101
`2381 <https://wg21.link/LWG2381>`__,"Inconsistency in parsing floating point numbers","October 2021","|Complete|","19.0"
102-
`2762 <https://wg21.link/LWG2762>`__,"``unique_ptr operator*()`` should be ``noexcept``","October 2021","",""
102+
`2762 <https://wg21.link/LWG2762>`__,"``unique_ptr operator*()`` should be ``noexcept``","October 2021","|Complete|","19.0"
103103
`3121 <https://wg21.link/LWG3121>`__,"``tuple`` constructor constraints for ``UTypes&&...`` overloads","October 2021","",""
104104
`3123 <https://wg21.link/LWG3123>`__,"``duration`` constructor from representation shouldn't be effectively non-throwing","October 2021","","","|chrono|"
105105
`3146 <https://wg21.link/LWG3146>`__,"Excessive unwrapping in ``std::ref/cref``","October 2021","|Complete|","14.0"

libcxx/include/__memory/unique_ptr.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
#include <__type_traits/is_trivially_relocatable.h>
3737
#include <__type_traits/is_void.h>
3838
#include <__type_traits/remove_extent.h>
39+
#include <__type_traits/remove_pointer.h>
3940
#include <__type_traits/type_identity.h>
41+
#include <__utility/declval.h>
4042
#include <__utility/forward.h>
4143
#include <__utility/move.h>
4244
#include <cstddef>
@@ -50,6 +52,17 @@ _LIBCPP_PUSH_MACROS
5052

5153
_LIBCPP_BEGIN_NAMESPACE_STD
5254

55+
#ifndef _LIBCPP_CXX03_LANG
56+
57+
template <class _Ptr>
58+
struct __is_noexcept_deref_or_void {
59+
static constexpr bool value = noexcept(*std::declval<_Ptr>());
60+
};
61+
62+
template <>
63+
struct __is_noexcept_deref_or_void<void*> : true_type {};
64+
#endif
65+
5366
template <class _Tp>
5467
struct _LIBCPP_TEMPLATE_VIS default_delete {
5568
static_assert(!is_function<_Tp>::value, "default_delete cannot be instantiated for function types");
@@ -252,7 +265,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
252265
return *this;
253266
}
254267

255-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 __add_lvalue_reference_t<_Tp> operator*() const {
268+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 __add_lvalue_reference_t<_Tp> operator*() const
269+
_NOEXCEPT_(__is_noexcept_deref_or_void<pointer>::value) {
256270
return *__ptr_.first();
257271
}
258272
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer operator->() const _NOEXCEPT { return __ptr_.first(); }

libcxx/include/memory

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,8 @@ public:
451451
constexpr unique_ptr& operator=(nullptr_t) noexcept; // constexpr since C++23
452452
453453
// observers
454-
typename constexpr add_lvalue_reference<T>::type operator*() const; // constexpr since C++23
454+
constexpr
455+
add_lvalue_reference<T>::type operator*() const noexcept(see below); // constexpr since C++23
455456
constexpr pointer operator->() const noexcept; // constexpr since C++23
456457
constexpr pointer get() const noexcept; // constexpr since C++23
457458
constexpr deleter_type& get_deleter() noexcept; // constexpr since C++23

libcxx/include/optional

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,12 @@ namespace std {
136136
void swap(optional &) noexcept(see below ); // constexpr in C++20
137137
138138
// [optional.observe], observers
139-
constexpr T const *operator->() const;
140-
constexpr T *operator->();
141-
constexpr T const &operator*() const &;
142-
constexpr T &operator*() &;
143-
constexpr T &&operator*() &&;
144-
constexpr const T &&operator*() const &&;
139+
constexpr T const *operator->() const noexcept;
140+
constexpr T *operator->() noexcept;
141+
constexpr T const &operator*() const & noexcept;
142+
constexpr T &operator*() & noexcept;
143+
constexpr T &&operator*() && noexcept;
144+
constexpr const T &&operator*() const && noexcept;
145145
constexpr explicit operator bool() const noexcept;
146146
constexpr bool has_value() const noexcept;
147147
constexpr T const &value() const &;
@@ -783,12 +783,12 @@ public:
783783
}
784784
}
785785

786-
_LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<value_type const> operator->() const {
786+
_LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<value_type const> operator->() const noexcept {
787787
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value");
788788
return std::addressof(this->__get());
789789
}
790790

791-
_LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<value_type> operator->() {
791+
_LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<value_type> operator->() noexcept {
792792
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value");
793793
return std::addressof(this->__get());
794794
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03
10+
11+
// unique_ptr
12+
13+
// add_lvalue_reference_t<T> operator*() const noexcept(noexcept(*declval<pointer>()));
14+
15+
// Dereferencing pointer directly in noexcept fails for a void pointer. This
16+
// is not SFINAE-ed away leading to a hard error. The issue was originally
17+
// triggered by
18+
// test/std/utilities/memory/unique.ptr/iterator_concept_conformance.compile.pass.cpp
19+
//
20+
// This test validates whether the code compiles.
21+
22+
#include <memory>
23+
24+
extern const std::unique_ptr<void> p;
25+
void f() { [[maybe_unused]] bool b = noexcept(p.operator*()); }

libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference.pass.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,7 @@ int main(int, char**)
4444
{
4545
optional<X> opt; ((void)opt);
4646
ASSERT_SAME_TYPE(decltype(*opt), X&);
47-
LIBCPP_STATIC_ASSERT(noexcept(*opt));
48-
// ASSERT_NOT_NOEXCEPT(*opt);
49-
// FIXME: This assertion fails with GCC because it can see that
50-
// (A) operator*() is constexpr, and
51-
// (B) there is no path through the function that throws.
52-
// It's arguable if this is the correct behavior for the noexcept
53-
// operator.
54-
// Regardless this function should still be noexcept(false) because
55-
// it has a narrow contract.
47+
ASSERT_NOEXCEPT(*opt);
5648
}
5749
{
5850
optional<X> opt(X{});

libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const.pass.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,7 @@ int main(int, char**)
3737
{
3838
const optional<X> opt; ((void)opt);
3939
ASSERT_SAME_TYPE(decltype(*opt), X const&);
40-
LIBCPP_STATIC_ASSERT(noexcept(*opt));
41-
// ASSERT_NOT_NOEXCEPT(*opt);
42-
// FIXME: This assertion fails with GCC because it can see that
43-
// (A) operator*() is constexpr, and
44-
// (B) there is no path through the function that throws.
45-
// It's arguable if this is the correct behavior for the noexcept
46-
// operator.
47-
// Regardless this function should still be noexcept(false) because
48-
// it has a narrow contract.
40+
ASSERT_NOEXCEPT(*opt);
4941
}
5042
{
5143
constexpr optional<X> opt(X{});

libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const_rvalue.pass.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,7 @@ int main(int, char**)
3737
{
3838
const optional<X> opt; ((void)opt);
3939
ASSERT_SAME_TYPE(decltype(*std::move(opt)), X const &&);
40-
LIBCPP_STATIC_ASSERT(noexcept(*opt));
41-
// ASSERT_NOT_NOEXCEPT(*std::move(opt));
42-
// FIXME: This assertion fails with GCC because it can see that
43-
// (A) operator*() is constexpr, and
44-
// (B) there is no path through the function that throws.
45-
// It's arguable if this is the correct behavior for the noexcept
46-
// operator.
47-
// Regardless this function should still be noexcept(false) because
48-
// it has a narrow contract.
40+
ASSERT_NOEXCEPT(*std::move(opt));
4941
}
5042
{
5143
constexpr optional<X> opt(X{});

libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_rvalue.pass.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,7 @@ int main(int, char**)
4444
{
4545
optional<X> opt; ((void)opt);
4646
ASSERT_SAME_TYPE(decltype(*std::move(opt)), X&&);
47-
LIBCPP_STATIC_ASSERT(noexcept(*opt));
48-
// ASSERT_NOT_NOEXCEPT(*std::move(opt));
49-
// FIXME: This assertion fails with GCC because it can see that
50-
// (A) operator*() is constexpr, and
51-
// (B) there is no path through the function that throws.
52-
// It's arguable if this is the correct behavior for the noexcept
53-
// operator.
54-
// Regardless this function should still be noexcept(false) because
55-
// it has a narrow contract.
47+
ASSERT_NOEXCEPT(*std::move(opt));
5648
}
5749
{
5850
optional<X> opt(X{});

libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow.pass.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,7 @@ int main(int, char**)
4141
{
4242
std::optional<X> opt; ((void)opt);
4343
ASSERT_SAME_TYPE(decltype(opt.operator->()), X*);
44-
// ASSERT_NOT_NOEXCEPT(opt.operator->());
45-
// FIXME: This assertion fails with GCC because it can see that
46-
// (A) operator->() is constexpr, and
47-
// (B) there is no path through the function that throws.
48-
// It's arguable if this is the correct behavior for the noexcept
49-
// operator.
50-
// Regardless this function should still be noexcept(false) because
51-
// it has a narrow contract.
44+
ASSERT_NOEXCEPT(opt.operator->());
5245
}
5346
{
5447
optional<X> opt(X{});

libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow_const.pass.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,7 @@ int main(int, char**)
4040
{
4141
const std::optional<X> opt; ((void)opt);
4242
ASSERT_SAME_TYPE(decltype(opt.operator->()), X const*);
43-
// ASSERT_NOT_NOEXCEPT(opt.operator->());
44-
// FIXME: This assertion fails with GCC because it can see that
45-
// (A) operator->() is constexpr, and
46-
// (B) there is no path through the function that throws.
47-
// It's arguable if this is the correct behavior for the noexcept
48-
// operator.
49-
// Regardless this function should still be noexcept(false) because
50-
// it has a narrow contract.
43+
ASSERT_NOEXCEPT(opt.operator->());
5144
}
5245
{
5346
constexpr optional<X> opt(X{});

libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/dereference.single.pass.cpp

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,50 @@
1414

1515
#include <memory>
1616
#include <cassert>
17+
#include <vector>
1718

1819
#include "test_macros.h"
1920

21+
#if TEST_STD_VER >= 11
22+
struct ThrowDereference {
23+
TEST_CONSTEXPR_CXX23 ThrowDereference& operator*() noexcept(false);
24+
TEST_CONSTEXPR_CXX23 operator bool() const { return false; }
25+
};
26+
27+
struct Deleter {
28+
using pointer = ThrowDereference;
29+
TEST_CONSTEXPR_CXX23 void operator()(ThrowDereference&) const {}
30+
};
31+
#endif
32+
2033
TEST_CONSTEXPR_CXX23 bool test() {
21-
std::unique_ptr<int> p(new int(3));
22-
assert(*p == 3);
34+
ASSERT_NOEXCEPT(*(std::unique_ptr<void>{}));
35+
#if TEST_STD_VER >= 11
36+
static_assert(noexcept(*std::declval<std::unique_ptr<void>>()), "");
37+
#endif
38+
{
39+
std::unique_ptr<int> p(new int(3));
40+
assert(*p == 3);
41+
ASSERT_NOEXCEPT(*p);
42+
}
43+
#if TEST_STD_VER >= 11
44+
{
45+
std::unique_ptr<std::vector<int>> p(new std::vector<int>{3, 4, 5});
46+
assert((*p)[0] == 3);
47+
assert((*p)[1] == 4);
48+
assert((*p)[2] == 5);
49+
ASSERT_NOEXCEPT(*p);
50+
}
51+
{
52+
std::unique_ptr<ThrowDereference> p;
53+
ASSERT_NOEXCEPT(*p);
54+
}
55+
{
56+
// The noexcept status of *unique_ptr<>::pointer should be propagated.
57+
std::unique_ptr<ThrowDereference, Deleter> p;
58+
ASSERT_NOT_NOEXCEPT(*p);
59+
}
60+
#endif
2361

2462
return true;
2563
}

0 commit comments

Comments
 (0)