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 @@ -49,6 +51,20 @@ _LIBCPP_BEGIN_NAMESPACE_STD +#ifndef _LIBCPP_CXX03_LANG +// Dereferencing _Ptr directly in noexcept fails for a void pointer. +// This is not SFINAE-ed away leading to a hard error. +// The issue was originally triggered by +// test/std/utilities/memory/unique.ptr/iterator_concept_conformance.compile.pass.cpp +template +struct __is_noexcept_deref_or_void { + static constexpr bool value = noexcept(*std::declval<_Ptr>()); +}; + +template <> +struct __is_noexcept_deref_or_void : true_type {}; +#endif + template struct _LIBCPP_TEMPLATE_VIS default_delete { static_assert(!is_function<_Tp>::value, @@ -267,7 +283,11 @@ 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 +#ifndef _LIBCPP_CXX03_LANG + noexcept(__is_noexcept_deref_or_void::value) +#endif + { 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 + 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 @@ -14,12 +14,47 @@ #include #include +#include #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); + ASSERT_NOEXCEPT(*(std::unique_ptr{})); + { + std::unique_ptr p(new int(3)); + assert(*p == 3); + ASSERT_NOEXCEPT(*p); + } +#if TEST_STD_VER >= 11 + { + std::unique_ptr> p(new std::vector{3, 4, 5}); + assert((*p)[0] == 3); + assert((*p)[1] == 4); + assert((*p)[2] == 5); + ASSERT_NOEXCEPT(*p); + } + { + std::unique_ptr p; + ASSERT_NOEXCEPT(*p); + } + { + // The noexcept status of *unique_ptr<>::pointer should be propagated. + std::unique_ptr p; + ASSERT_NOT_NOEXCEPT(*p); + } +#endif return true; }