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 @@ -291,7 +291,7 @@ "`3828 `__","Sync ``intmax_t`` and ``uintmax_t`` with C2x","February 2023","","","" "`3833 `__","Remove specialization ``template struct formatter``","February 2023","|Complete|","17.0","|format|" "`3836 `__","``std::expected`` conversion constructor ``expected(const expected&)`` should take precedence over ``expected(U&&)`` with operator ``bool``","February 2023","","","" -"`3843 `__","``std::expected::value() &`` assumes ``E`` is copy constructible","February 2023","","","" +"`3843 `__","``std::expected::value() &`` assumes ``E`` is copy constructible","February 2023","|Complete|","17.0","" "`3847 `__","``ranges::to`` can still return views","February 2023","","","|ranges|" "`3862 `__","``basic_const_iterator``'s ``common_type`` specialization is underconstrained","February 2023","","","" "`3865 `__","Sorting a range of ``pairs``","February 2023","|Complete|","17.0","|ranges|" diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h --- a/libcxx/include/__expected/expected.h +++ b/libcxx/include/__expected/expected.h @@ -46,6 +46,7 @@ #include <__type_traits/negation.h> #include <__type_traits/remove_cv.h> #include <__type_traits/remove_cvref.h> +#include <__utility/as_const.h> #include <__utility/exception_guard.h> #include <__utility/forward.h> #include <__utility/in_place.h> @@ -559,29 +560,35 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return __has_val_; } _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& value() const& { + static_assert(is_copy_constructible_v<_Err>, "error_type has to be copy constructible"); if (!__has_val_) { - std::__throw_bad_expected_access<_Err>(__union_.__unex_); + std::__throw_bad_expected_access<_Err>(std::as_const(error())); } return __union_.__val_; } _LIBCPP_HIDE_FROM_ABI constexpr _Tp& value() & { + static_assert(is_copy_constructible_v<_Err>, "error_type has to be copy constructible"); if (!__has_val_) { - std::__throw_bad_expected_access<_Err>(__union_.__unex_); + std::__throw_bad_expected_access<_Err>(std::as_const(error())); } return __union_.__val_; } _LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& value() const&& { + static_assert(is_copy_constructible_v<_Err> && is_constructible_v<_Err, decltype(std::move(error()))>, + "error_type has to be both copy constructible and constructible from decltype(std::move(error()))"); if (!__has_val_) { - std::__throw_bad_expected_access<_Err>(std::move(__union_.__unex_)); + std::__throw_bad_expected_access<_Err>(std::move(error())); } return std::move(__union_.__val_); } _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& value() && { + static_assert(is_copy_constructible_v<_Err> && is_constructible_v<_Err, decltype(std::move(error()))>, + "error_type has to be both copy constructible and constructible from decltype(std::move(error()))"); if (!__has_val_) { - std::__throw_bad_expected_access<_Err>(std::move(__union_.__unex_)); + std::__throw_bad_expected_access<_Err>(std::move(error())); } return std::move(__union_.__val_); } diff --git a/libcxx/test/libcxx/utilities/expected/expected.expected/value.observers.verify.cpp b/libcxx/test/libcxx/utilities/expected/expected.expected/value.observers.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/expected/expected.expected/value.observers.verify.cpp @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 + +// Test the mandates +// constexpr T& value() & ; +// constexpr const T& value() const &; +// Mandates: is_copy_constructible_v is true. + +// constexpr T&& value() &&; +// constexpr const T&& value() const &&; +// Mandates: is_copy_constructible_v is true and is_constructible_v is true. + +#include +#include + +#include "MoveOnly.h" + +struct CopyConstructible { + constexpr CopyConstructible() = default; + constexpr CopyConstructible(const CopyConstructible&) = default; +}; + +struct CopyConstructibleButNotMoveConstructible { + constexpr CopyConstructibleButNotMoveConstructible() = default; + constexpr CopyConstructibleButNotMoveConstructible(const CopyConstructibleButNotMoveConstructible&) = default; + constexpr CopyConstructibleButNotMoveConstructible(CopyConstructibleButNotMoveConstructible&&) = delete; + constexpr CopyConstructibleButNotMoveConstructible(const CopyConstructibleButNotMoveConstructible&&) = delete; +}; + +struct CopyConstructibleAndMoveConstructible { + constexpr CopyConstructibleAndMoveConstructible() = default; + constexpr CopyConstructibleAndMoveConstructible(const CopyConstructibleAndMoveConstructible&) = default; + constexpr CopyConstructibleAndMoveConstructible(CopyConstructibleAndMoveConstructible&&) = default; +}; + +// clang-format off +void test() { + + // Test & overload + { + // is_copy_constructible_v is true. + { + std::expected e; + [[maybe_unused]] auto val = e.value(); + } + + // is_copy_constructible_v is false. + { + std::expected e; + [[maybe_unused]] auto val = e.value(); + // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be copy constructible}} + } + } + + // Test const& overload + { + // is_copy_constructible_v is true. + { + const std::expected e; + [[maybe_unused]] auto val = e.value(); + } + + // is_copy_constructible_v is false. + { + const std::expected e; + [[maybe_unused]] auto val = e.value(); + // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be copy constructible}} + } + } + + // Test && overload + { + // is_copy_constructible_v is false. + { + std::expected e; + [[maybe_unused]] auto val = std::move(e).value(); + // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be both copy constructible and constructible from decltype(std::move(error()))}} + } + + // is_copy_constructible_v is true and is_constructible_v is true. + { + std::expected e; + [[maybe_unused]] auto val = std::move(e).value(); + } + + // is_copy_constructible_v is true and is_constructible_v is false. + { + std::expected e; + [[maybe_unused]] auto val = std::move(e).value(); + // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be both copy constructible and constructible from decltype(std::move(error()))}} + } + } + + // Test const&& overload + { + // is_copy_constructible_v is false. + { + const std::expected e; + [[maybe_unused]] auto val = std::move(e).value(); + // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be both copy constructible and constructible from decltype(std::move(error()))}} + } + + // is_copy_constructible_v is true and is_constructible_v is true. + { + const std::expected e; + [[maybe_unused]] auto val = std::move(e).value(); + } + + // is_copy_constructible_v is true and is_constructible_v is false. + { + const std::expected e; + [[maybe_unused]] auto val = std::move(e).value(); + // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}error_type has to be both copy constructible and constructible from decltype(std::move(error()))}} + } + } +// These diagnostics happen when we try to construct bad_expected_access from the non copy-constructible error type. +#ifndef _LIBCPP_HAS_NO_EXCEPTIONS + // expected-error-re@*:* {{call to deleted constructor of{{.*}}}} + // expected-error-re@*:* {{call to deleted constructor of{{.*}}}} + // expected-error-re@*:* {{call to deleted constructor of{{.*}}}} + // expected-error-re@*:* {{call to deleted constructor of{{.*}}}} +#endif +} +// clang-format on diff --git a/libcxx/test/std/utilities/expected/expected.expected/observers/value.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/observers/value.pass.cpp --- a/libcxx/test/std/utilities/expected/expected.expected/observers/value.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/observers/value.pass.cpp @@ -18,7 +18,6 @@ #include #include -#include "MoveOnly.h" #include "test_macros.h" constexpr bool test() { @@ -75,17 +74,32 @@ } } - // MoveOnly +#endif // TEST_HAS_NO_EXCEPTIONS +} + +void testAsConst() { +#ifndef TEST_HAS_NO_EXCEPTIONS + struct Error { + enum { Default, MutableRefCalled, ConstRefCalled } From = Default; + Error() = default; + Error(const Error&) { From = ConstRefCalled; } + Error(Error&) { From = MutableRefCalled; } + Error(Error&& e) { From = e.From; } + }; + + // Test & overload { - std::expected e(std::unexpect, 5); + std::expected e(std::unexpect, Error()); try { - (void) std::move(e).value(); + (void)e.value(); assert(false); - } catch (const std::bad_expected_access& ex) { - assert(ex.error() == 5); + } catch (const std::bad_expected_access& ex) { + assert(ex.error().From == Error::ConstRefCalled); } } + // There are no effects for `const &` overload. + #endif // TEST_HAS_NO_EXCEPTIONS } @@ -93,5 +107,7 @@ test(); static_assert(test()); testException(); + testAsConst(); + return 0; }