diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -155,6 +155,7 @@ __ranges/access.h __ranges/all.h __ranges/concepts.h + __ranges/copyable_box.h __ranges/data.h __ranges/drop_view.h __ranges/empty.h diff --git a/libcxx/include/__ranges/copyable_box.h b/libcxx/include/__ranges/copyable_box.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__ranges/copyable_box.h @@ -0,0 +1,155 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___RANGES_COPYABLE_BOX_H +#define _LIBCPP___RANGES_COPYABLE_BOX_H + +#include <__config> +#include <__memory/addressof.h> +#include <__memory/construct_at.h> +#include <__utility/move.h> +#include +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if !defined(_LIBCPP_HAS_NO_RANGES) + +// __copyable_box allows turning a type that is copy-constructible (but maybe not copy-assignable) into +// a type that is both copy-constructible and copy-assignable. It does that by introducing an empty state +// and basically doing destroy-then-copy-construct in the assignment operator. The empty state is necessary +// to handle the case where the copy construction fails after destroying the object. +// +// Types that are copy-assignable don't need any of this trickery, so we don't need to store an empty state. +// Types that are nothrow-copy-constructible also don't need this trickery, since we know we'll never get into +// that empty state. We provide specializations of __copyable_box below to handle those cases more efficiently. +// +// Also note that all of the above also applies to the move-assignment operator, for which we use the move +// constructor of the underlying type. + +template +concept __copy_constructible_object = copy_constructible<_Tp> && is_object_v<_Tp>; + +namespace ranges { + // Primary template - uses std::optional and introduces an empty state in case assignment fails. + template<__copy_constructible_object _Tp> + class __copyable_box { + std::optional<_Tp> __val_; + + public: + template + requires is_constructible_v<_Tp, _Args...> + constexpr explicit __copyable_box(in_place_t, _Args&& ...__args) + noexcept(is_nothrow_constructible_v<_Tp, _Args...>) + : __val_(in_place, _VSTD::forward<_Args>(__args)...) + { } + + constexpr __copyable_box() noexcept(is_nothrow_default_constructible_v<_Tp>) + requires default_initializable<_Tp> + : __val_{in_place} // braces intentional + { } + + __copyable_box(__copyable_box const&) = default; + __copyable_box(__copyable_box&&) = default; + + constexpr __copyable_box& operator=(__copyable_box const& __other) + noexcept(is_nothrow_copy_constructible_v<_Tp>) + { + if (this != _VSTD::addressof(__other)) { + if (__other) __val_.emplace(*__other); + else __val_.reset(); + } + return *this; + } + + constexpr __copyable_box& operator=(__copyable_box&& __other) + noexcept(is_nothrow_move_constructible_v<_Tp>) + { + if (this != _VSTD::addressof(__other)) { + if (__other) __val_.emplace(_VSTD::move(*__other)); + else __val_.reset(); + } + return *this; + } + + constexpr _Tp const& operator*() const { return *__val_; } + constexpr _Tp& operator*() { return *__val_; } + constexpr explicit operator bool() const noexcept { return static_cast(__val_); } + }; + + // This partial specialization implements an optimization for when we know we don't need to store + // an empty state to represent failure to perform an assignment. This happens in two cases: + // + // 1. If the type is copyable (which includes copy-assignment, and also implies the type is movable), we can + // use the type's own assignment operators directly and avoid using std::optional. + // 2. If the type is not copyable, but it is nothrow-copy-constructible and nothrow-move-constructible, then + // we can implement assignment as destroy-and-then-construct and we know it will never fail. + template<__copy_constructible_object _Tp> + requires copyable<_Tp> || (is_nothrow_copy_constructible_v<_Tp> && is_nothrow_move_constructible_v<_Tp>) + class __copyable_box<_Tp> { + [[no_unique_address]] _Tp __val_; + + public: + template + requires is_constructible_v<_Tp, _Args...> + constexpr explicit __copyable_box(in_place_t, _Args&& ...__args) + noexcept(is_nothrow_constructible_v<_Tp, _Args...>) + : __val_(_VSTD::forward<_Args>(__args)...) + { } + + constexpr __copyable_box() noexcept(is_nothrow_default_constructible_v<_Tp>) + requires default_initializable<_Tp> + : __val_() + { } + + __copyable_box(__copyable_box const&) = default; + __copyable_box(__copyable_box&&) = default; + + // Implementation of assignment operators in case we perform optimization (1) + __copyable_box& operator=(__copyable_box const&) requires copyable<_Tp> = default; + __copyable_box& operator=(__copyable_box&&) requires copyable<_Tp> = default; + + // Implementation of assignment operators in case we perform optimization (2) + constexpr __copyable_box& operator=(__copyable_box const& __other) noexcept requires (!copyable<_Tp>) { + if (this != _VSTD::addressof(__other)) { + _VSTD::destroy_at(&__val_); + _VSTD::construct_at(&__val_, __other.__val_); + } + return *this; + } + + constexpr __copyable_box& operator=(__copyable_box&& __other) noexcept requires (!copyable<_Tp>) { + if (this != _VSTD::addressof(__other)) { + _VSTD::destroy_at(&__val_); + _VSTD::construct_at(&__val_, _VSTD::move(__other.__val_)); + } + return *this; + } + + constexpr _Tp const& operator*() const { return __val_; } + constexpr _Tp& operator*() { return __val_; } + constexpr explicit operator bool() const noexcept { return true; } + }; +} // namespace ranges + +#endif // !defined(_LIBCPP_HAS_NO_RANGES) + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___RANGES_COPYABLE_BOX_H diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -570,6 +570,7 @@ module access { header "__ranges/access.h" } module all { header "__ranges/all.h" } module concepts { header "__ranges/concepts.h" } + module copyable_box { header "__ranges/copyable_box.h" } module data { header "__ranges/data.h" } module empty { header "__ranges/empty.h" } module empty_view { header "__ranges/empty_view.h" } diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/assign.copy.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/assign.copy.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/assign.copy.pass.cpp @@ -0,0 +1,160 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: gcc-10 + +// & operator=( const&) + +#include <__ranges/copyable_box.h> + +#include +#include + +#include "types.h" + +#pragma GCC diagnostic ignored "-Wself-assign-overloaded" + +constexpr bool test() { + // Test the primary template + { + using Box = std::ranges::__copyable_box; + static_assert(std::is_copy_assignable_v); + static_assert(!std::is_nothrow_copy_assignable_v); + + { + Box x(std::in_place, 5); + Box const y(std::in_place, 10); + Box& result = (x = y); + + assert(&result == &x); + assert(x); + assert(y); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = x); + + assert(&result == &x); + assert(x); + assert((*x).value == 5); + } + } + + // Test optimization #1 + { + using Box = std::ranges::__copyable_box; + static_assert(std::is_copy_assignable_v); + static_assert(!std::is_nothrow_copy_assignable_v); + + { + Box x(std::in_place, 5); + Box const y(std::in_place, 10); + Box& result = (x = y); + + assert(&result == &x); + assert(x); + assert(y); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = x); + + assert(&result == &x); + assert(x); + assert((*x).value == 5); + } + } + + // Test optimization #2 + { + using Box = std::ranges::__copyable_box; + static_assert(std::is_copy_assignable_v); + static_assert(std::is_nothrow_copy_assignable_v); + + { + Box x(std::in_place, 5); + Box const y(std::in_place, 10); + Box& result = (x = y); + + assert(&result == &x); + assert(x); + assert(y); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = x); + + assert(&result == &x); + assert(x); + assert((*x).value == 5); + } + } + + return true; +} + +int main(int, char**) { + assert(test()); + static_assert(test()); + + // Tests for the empty state. Those can't be constexpr, since they are only reached + // through throwing an exception. + { + using Box = std::ranges::__copyable_box; + + // assign non-empty to empty + { + Box x = create_empty_box(); + Box const y(std::in_place, 10); + Box& result = (x = y); + + assert(&result == &x); + assert(x); + assert(y); + assert((*x).value == 10); + } + // assign empty to non-empty + { + Box x(std::in_place, 5); + Box const y = create_empty_box(); + Box& result = (x = y); + + assert(&result == &x); + assert(!x); + assert(!y); + } + // assign empty to empty + { + Box x = create_empty_box(); + Box const y = create_empty_box(); + Box& result = (x = y); + + assert(&result == &x); + assert(!x); + assert(!y); + } + // check self-assignment in empty case + { + Box x = create_empty_box(); + Box& result = (x = x); + + assert(&result == &x); + assert(!x); + } + } + + return 0; +} diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/assign.move.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/assign.move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/assign.move.pass.cpp @@ -0,0 +1,160 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: gcc-10 + +// & operator=(&&) + +#include <__ranges/copyable_box.h> + +#include +#include + +#include "types.h" + +#pragma GCC diagnostic ignored "-Wself-move" + +constexpr bool test() { + // Test the primary template + { + using Box = std::ranges::__copyable_box; + static_assert(std::is_move_assignable_v); + static_assert(!std::is_nothrow_move_assignable_v); + + { + Box x(std::in_place, 5); + Box y(std::in_place, 10); + Box& result = (x = std::move(y)); + + assert(&result == &x); + assert(x); + assert(y); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = std::move(x)); + + assert(&result == &x); + assert(x); + assert((*x).value == 5); + } + } + + // Test optimization #1 + { + using Box = std::ranges::__copyable_box; + static_assert(std::is_copy_assignable_v); + static_assert(!std::is_nothrow_copy_assignable_v); + + { + Box x(std::in_place, 5); + Box y(std::in_place, 10); + Box& result = (x = std::move(y)); + + assert(&result == &x); + assert(x); + assert(y); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = std::move(x)); + + assert(&result == &x); + assert(x); + assert((*x).value == 5); + } + } + + // Test optimization #2 + { + using Box = std::ranges::__copyable_box; + static_assert(std::is_copy_assignable_v); + static_assert(std::is_nothrow_copy_assignable_v); + + { + Box x(std::in_place, 5); + Box y(std::in_place, 10); + Box& result = (x = std::move(y)); + + assert(&result == &x); + assert(x); + assert(y); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = std::move(x)); + + assert(&result == &x); + assert(x); + assert((*x).value == 5); + } + } + + return true; +} + +int main(int, char**) { + assert(test()); + static_assert(test()); + + // Tests for the empty state. Those can't be constexpr, since they are only reached + // through throwing an exception. + { + using Box = std::ranges::__copyable_box; + + // assign non-empty to empty + { + Box x = create_empty_box(); + Box y(std::in_place, 10); + Box& result = (x = std::move(y)); + + assert(&result == &x); + assert(x); + assert(y); + assert((*x).value == 10); + } + // assign empty to non-empty + { + Box x(std::in_place, 5); + Box y = create_empty_box(); + Box& result = (x = std::move(y)); + + assert(&result == &x); + assert(!x); + assert(!y); + } + // assign empty to empty + { + Box x = create_empty_box(); + Box y = create_empty_box(); + Box& result = (x = std::move(y)); + + assert(&result == &x); + assert(!x); + assert(!y); + } + // check self-assignment in empty case + { + Box x = create_empty_box(); + Box& result = (x = std::move(x)); + + assert(&result == &x); + assert(!x); + } + } + + return 0; +} diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/ctor.default.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/ctor.default.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/ctor.default.pass.cpp @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: gcc-10 + +// ::() + +#include <__ranges/copyable_box.h> + +#include + +#include "types.h" + +template +using Box = std::ranges::__copyable_box; + +struct NoDefault { + NoDefault() = delete; +}; +static_assert(!std::is_default_constructible_v>); + +template +struct DefaultNoexcept { + DefaultNoexcept() noexcept(Noexcept) = default; +}; +static_assert(std::is_nothrow_default_constructible_v>>); +static_assert(!std::is_nothrow_default_constructible_v>>); + +constexpr bool test() { + // check primary template + { + Box box; + assert(box); + assert((*box).value == CopyConstructible().value); + } + + // check optimization #1 + { + Box box; + assert(box); + assert((*box).value == Copyable().value); + } + + // check optimization #2 + { + Box box; + assert(box); + assert((*box).value == NothrowCopyConstructible().value); + } + + return true; +} + +int main(int, char**) { + assert(test()); + static_assert(test()); + return 0; +} diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/deref.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/deref.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/deref.pass.cpp @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: gcc-10 + +// T& ::operator*() +// T const& ::operator*() const + +#include <__ranges/copyable_box.h> + +#include + +#include "types.h" + +template +constexpr void check() { + // non-const version + { + std::ranges::__copyable_box x(std::in_place, 10); + T& result = *x; + assert(result.value == 10); + } + + // const version + { + std::ranges::__copyable_box const x(std::in_place, 10); + T const& result = *x; + assert(result.value == 10); + } +} + +constexpr bool test() { + check(); // primary template + check(); // optimization #1 + check(); // optimization #2 + return true; +} + +int main(int, char**) { + assert(test()); + static_assert(test()); + return 0; +} diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/operator-bool.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/operator-bool.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/operator-bool.pass.cpp @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: gcc-10 + +// explicit bool ::operator bool() const + +#include <__ranges/copyable_box.h> + +#include + +#include "types.h" + +template +constexpr void check() { + std::ranges::__copyable_box const x(std::in_place, 10); + bool result = static_cast(x); + assert(result); + + static_assert(!std::is_convertible_v, bool>); + static_assert(std::is_constructible_v>); +} + +constexpr bool test() { + check(); // primary template + check(); // optimization #1 + check(); // optimization #2 + return true; +} + +int main(int, char**) { + assert(test()); + static_assert(test()); + + // Tests for the empty state. Those can't be constexpr, since they are only reached + // through throwing an exception. + { + std::ranges::__copyable_box x = create_empty_box(); + bool result = static_cast(x); + assert(!result); + } + + return 0; +} diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/properties.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/properties.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/properties.compile.pass.cpp @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: gcc-10 + +// Test various properties of + +#include <__ranges/copyable_box.h> + +#include "types.h" + +template +constexpr bool valid_copyable_box = requires { + typename std::ranges::__copyable_box; +}; + +struct NotCopyConstructible { + NotCopyConstructible() = default; + NotCopyConstructible(NotCopyConstructible&&) = default; + NotCopyConstructible(NotCopyConstructible const&) = delete; + NotCopyConstructible& operator=(NotCopyConstructible&&) = default; + NotCopyConstructible& operator=(NotCopyConstructible const&) = default; +}; + +static_assert(!valid_copyable_box); // not an object type +static_assert(!valid_copyable_box); // not an object type +static_assert(!valid_copyable_box); + +// primary template +static_assert(sizeof(std::ranges::__copyable_box) > sizeof(CopyConstructible)); // must have an empty state + +// optimization #1 +static_assert(sizeof(std::ranges::__copyable_box) == sizeof(Copyable)); +static_assert(alignof(std::ranges::__copyable_box) == alignof(Copyable)); + +// optimization #2 +static_assert(sizeof(std::ranges::__copyable_box) == sizeof(NothrowCopyConstructible)); +static_assert(alignof(std::ranges::__copyable_box) == alignof(NothrowCopyConstructible)); diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/types.h b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/types.h new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/types.h @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_COPY_WRAP_TYPES_H +#define TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_COPY_WRAP_TYPES_H + +#include +#include <__ranges/copyable_box.h> + +// Copy constructible, but neither copyable nor nothrow_copy/move_constructible. This uses the primary template. +struct CopyConstructible { + constexpr CopyConstructible() = default; + constexpr explicit CopyConstructible(int x) : value(x) { } + CopyConstructible(CopyConstructible const&) noexcept(false) = default; // prevent nothrow_xxx_constructible + CopyConstructible& operator=(CopyConstructible const&) = delete; // prevent from being copyable + + int value = -1; +}; + +// Copyable type that is not nothrow_copy/move_constructible. This triggers optimization #1 in __copyable_box. +struct Copyable { + constexpr Copyable() = default; + constexpr explicit Copyable(int x) : value(x) { } + Copyable(Copyable const&) = default; + Copyable& operator=(Copyable const&) noexcept(false) = default; + + int value = -1; +}; + +// nothrow_copy/move_constructible type that is not copyable. This triggers optimization #2 in __copyable_box. +struct NothrowCopyConstructible { + constexpr NothrowCopyConstructible() = default; + constexpr explicit NothrowCopyConstructible(int x) : value(x) { } + NothrowCopyConstructible(NothrowCopyConstructible const&) noexcept = default; + NothrowCopyConstructible(NothrowCopyConstructible&&) noexcept = default; + NothrowCopyConstructible& operator=(NothrowCopyConstructible const&) = delete; // prevent from being copyable + + int value = -1; +}; + +// A type that we can make throw when copied from. This is used to create a +// copyable-box in the empty state. +static constexpr int THROW_WHEN_COPIED_FROM = 999; +struct ThrowsOnCopy { + constexpr ThrowsOnCopy() = default; + constexpr explicit ThrowsOnCopy(int x) : value(x) { } + ThrowsOnCopy(ThrowsOnCopy const& other) { + if (other.value == THROW_WHEN_COPIED_FROM) throw 0; + else value = other.value; + } + + ThrowsOnCopy& operator=(ThrowsOnCopy const&) = delete; // prevent from being copyable + + int value = -1; +}; + +// Creates an empty box. The only way to do that is to try assigning one box +// to another and have that fail due to an exception when calling the copy +// constructor. The assigned-to box will then be in the empty state. +inline std::ranges::__copyable_box create_empty_box() { + std::ranges::__copyable_box box1; + std::ranges::__copyable_box box2(std::in_place, THROW_WHEN_COPIED_FROM); + try { + box1 = box2; // throws during assignment, which is implemented as a call to the copy ctor + } catch (...) { + // now, box1 is empty + assert(!box1); + return box1; + } + assert(false && "should never be reached"); +} + +#endif // TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_COPY_WRAP_TYPES_H