diff --git a/libcxx/docs/Status/Cxx23Issues.csv b/libcxx/docs/Status/Cxx23Issues.csv --- a/libcxx/docs/Status/Cxx23Issues.csv +++ b/libcxx/docs/Status/Cxx23Issues.csv @@ -99,7 +99,7 @@ "","","","","","" `2191 `__,"Incorrect specification of ``match_results(match_results&&)``","October 2021","|Nothing To Do|","" `2381 `__,"Inconsistency in parsing floating point numbers","October 2021","","" -`2762 `__,"``unique_ptr operator*()`` should be ``noexcept``","October 2021","","" +`2762 `__,"``unique_ptr operator*()`` should be ``noexcept``","October 2021","|Complete|","18.0" `3121 `__,"``tuple`` constructor constraints for ``UTypes&&...`` overloads","October 2021","","" `3123 `__,"``duration`` constructor from representation shouldn't be effectively non-throwing","October 2021","","","|chrono|" `3146 `__,"Excessive unwrapping in ``std::ref/cref``","October 2021","|Complete|","14.0" diff --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h --- a/libcxx/include/__memory/unique_ptr.h +++ b/libcxx/include/__memory/unique_ptr.h @@ -35,7 +35,9 @@ #include <__type_traits/is_swappable.h> #include <__type_traits/is_void.h> #include <__type_traits/remove_extent.h> +#include <__type_traits/remove_pointer.h> #include <__type_traits/type_identity.h> +#include <__utility/declval.h> #include <__utility/forward.h> #include <__utility/move.h> #include @@ -267,7 +269,21 @@ return *this; } - _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_SINCE_CXX23 __add_lvalue_reference_t<_Tp> operator*() const { + _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_SINCE_CXX23 __add_lvalue_reference_t<_Tp> operator*() const +#if _LIBCPP_STD_VER >= 20 + noexcept([] { + if constexpr (is_void_v>) + return true; + else + // This avoids evaluating noexcept(*declval() when pointer == void*. + // The issue was triggered by + // test/std/utilities/memory/unique.ptr/iterator_concept_conformance.compile.pass.cpp + return noexcept(*std::declval()); + }()) +#else // _LIBCPP_STD_VER >= 20 + _NOEXCEPT_(noexcept(*std::declval())) +#endif // _LIBCPP_STD_VER >= 20 + { return *__ptr_.first(); } _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer operator->() const _NOEXCEPT { diff --git a/libcxx/include/memory b/libcxx/include/memory --- a/libcxx/include/memory +++ b/libcxx/include/memory @@ -450,7 +450,8 @@ constexpr unique_ptr& operator=(nullptr_t) noexcept; // constexpr since C++23 // observers - typename constexpr add_lvalue_reference::type operator*() const; // constexpr since C++23 + typename constexpr + add_lvalue_reference::type operator*() const noexcept(see below); // constexpr since C++23 constexpr pointer operator->() const noexcept; // constexpr since C++23 constexpr pointer get() const noexcept; // constexpr since C++23 constexpr deleter_type& get_deleter() noexcept; // constexpr since C++23 diff --git a/libcxx/include/optional b/libcxx/include/optional --- a/libcxx/include/optional +++ b/libcxx/include/optional @@ -136,12 +136,12 @@ void swap(optional &) noexcept(see below ); // constexpr in C++20 // [optional.observe], observers - constexpr T const *operator->() const; - constexpr T *operator->(); - constexpr T const &operator*() const &; - constexpr T &operator*() &; - constexpr T &&operator*() &&; - constexpr const T &&operator*() const &&; + constexpr T const *operator->() const noexcept; + constexpr T *operator->() noexcept; + constexpr T const &operator*() const & noexcept; + constexpr T &operator*() & noexcept; + constexpr T &&operator*() && noexcept; + constexpr const T &&operator*() const && noexcept; constexpr explicit operator bool() const noexcept; constexpr bool has_value() const noexcept; constexpr T const &value() const &; @@ -998,7 +998,7 @@ _LIBCPP_INLINE_VISIBILITY constexpr add_pointer_t - operator->() const + operator->() const noexcept { _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value"); return _VSTD::addressof(this->__get()); @@ -1007,7 +1007,7 @@ _LIBCPP_INLINE_VISIBILITY constexpr add_pointer_t - operator->() + operator->() noexcept { _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value"); return _VSTD::addressof(this->__get()); diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference.pass.cpp --- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference.pass.cpp @@ -44,15 +44,7 @@ { optional opt; ((void)opt); ASSERT_SAME_TYPE(decltype(*opt), X&); - LIBCPP_STATIC_ASSERT(noexcept(*opt)); - // ASSERT_NOT_NOEXCEPT(*opt); - // FIXME: This assertion fails with GCC because it can see that - // (A) operator*() is constexpr, and - // (B) there is no path through the function that throws. - // It's arguable if this is the correct behavior for the noexcept - // operator. - // Regardless this function should still be noexcept(false) because - // it has a narrow contract. + ASSERT_NOEXCEPT(*opt); } { optional opt(X{}); diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const.pass.cpp --- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const.pass.cpp @@ -37,15 +37,7 @@ { const optional opt; ((void)opt); ASSERT_SAME_TYPE(decltype(*opt), X const&); - LIBCPP_STATIC_ASSERT(noexcept(*opt)); - // ASSERT_NOT_NOEXCEPT(*opt); - // FIXME: This assertion fails with GCC because it can see that - // (A) operator*() is constexpr, and - // (B) there is no path through the function that throws. - // It's arguable if this is the correct behavior for the noexcept - // operator. - // Regardless this function should still be noexcept(false) because - // it has a narrow contract. + ASSERT_NOEXCEPT(*opt); } { constexpr optional opt(X{}); diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const_rvalue.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const_rvalue.pass.cpp --- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const_rvalue.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const_rvalue.pass.cpp @@ -37,15 +37,7 @@ { const optional opt; ((void)opt); ASSERT_SAME_TYPE(decltype(*std::move(opt)), X const &&); - LIBCPP_STATIC_ASSERT(noexcept(*opt)); - // ASSERT_NOT_NOEXCEPT(*std::move(opt)); - // FIXME: This assertion fails with GCC because it can see that - // (A) operator*() is constexpr, and - // (B) there is no path through the function that throws. - // It's arguable if this is the correct behavior for the noexcept - // operator. - // Regardless this function should still be noexcept(false) because - // it has a narrow contract. + ASSERT_NOEXCEPT(*std::move(opt)); } { constexpr optional opt(X{}); diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_rvalue.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_rvalue.pass.cpp --- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_rvalue.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_rvalue.pass.cpp @@ -44,15 +44,7 @@ { optional opt; ((void)opt); ASSERT_SAME_TYPE(decltype(*std::move(opt)), X&&); - LIBCPP_STATIC_ASSERT(noexcept(*opt)); - // ASSERT_NOT_NOEXCEPT(*std::move(opt)); - // FIXME: This assertion fails with GCC because it can see that - // (A) operator*() is constexpr, and - // (B) there is no path through the function that throws. - // It's arguable if this is the correct behavior for the noexcept - // operator. - // Regardless this function should still be noexcept(false) because - // it has a narrow contract. + ASSERT_NOEXCEPT(*std::move(opt)); } { optional opt(X{}); diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow.pass.cpp --- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow.pass.cpp @@ -41,14 +41,7 @@ { std::optional opt; ((void)opt); ASSERT_SAME_TYPE(decltype(opt.operator->()), X*); - // ASSERT_NOT_NOEXCEPT(opt.operator->()); - // FIXME: This assertion fails with GCC because it can see that - // (A) operator->() is constexpr, and - // (B) there is no path through the function that throws. - // It's arguable if this is the correct behavior for the noexcept - // operator. - // Regardless this function should still be noexcept(false) because - // it has a narrow contract. + ASSERT_NOEXCEPT(opt.operator->()); } { optional opt(X{}); diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow_const.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow_const.pass.cpp --- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow_const.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow_const.pass.cpp @@ -40,14 +40,7 @@ { const std::optional opt; ((void)opt); ASSERT_SAME_TYPE(decltype(opt.operator->()), X const*); - // ASSERT_NOT_NOEXCEPT(opt.operator->()); - // FIXME: This assertion fails with GCC because it can see that - // (A) operator->() is constexpr, and - // (B) there is no path through the function that throws. - // It's arguable if this is the correct behavior for the noexcept - // operator. - // Regardless this function should still be noexcept(false) because - // it has a narrow contract. + ASSERT_NOEXCEPT(opt.operator->()); } { constexpr optional opt(X{}); diff --git a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/dereference.single.pass.cpp b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/dereference.single.pass.cpp --- a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/dereference.single.pass.cpp +++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.observers/dereference.single.pass.cpp @@ -17,9 +17,31 @@ #include "test_macros.h" +#if TEST_STD_VER >= 11 +struct ThrowDereference { + TEST_CONSTEXPR_CXX23 ThrowDereference& operator*() noexcept(false); + TEST_CONSTEXPR_CXX23 operator bool() const { return false; } +}; + +struct Deleter { + using pointer = ThrowDereference; + TEST_CONSTEXPR_CXX23 void operator()(ThrowDereference&) const {} +}; +#endif + TEST_CONSTEXPR_CXX23 bool test() { - std::unique_ptr p(new int(3)); - assert(*p == 3); + { + std::unique_ptr p(new int(3)); + assert(*p == 3); + ASSERT_NOEXCEPT(*p); + } +#if TEST_STD_VER >= 11 + { + // The noexcept status of *unique_ptr<>::pointer should be propagated. + std::unique_ptr p; + ASSERT_NOT_NOEXCEPT(*p); + } +#endif return true; }