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,164 @@ +// -*- 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. +// +// In some cases, we can completely avoid the use of an empty state; we provide a specialization of +// __copyable_box that does this, see below for the details. + +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 { + 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) + { } + + __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.__has_value()) __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.__has_value()) __val_.emplace(_VSTD::move(*__other)); + else __val_.reset(); + } + return *this; + } + + constexpr _Tp const& operator*() const { return *__val_; } + constexpr _Tp& operator*() { return *__val_; } + constexpr bool __has_value() const noexcept { return __val_.has_value(); } + }; + + // 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. For copy-assignment, this happens: + // + // 1. If the type is copyable (which includes copy-assignment), we can use the type's own assignment operator + // directly and avoid using std::optional. + // 2. If the type is not copyable, but it is nothrow-copy-constructible, then we can implement assignment as + // destroy-and-then-construct and we know it will never fail, so we don't need an empty state. + // + // The exact same reasoning can be applied for move-assignment, with copyable replaced by movable and + // nothrow-copy-constructible replaced by nothrow-move-constructible. This specialization is enabled + // whenever we can apply any of these optimizations for both the copy assignment and the move assignment + // operator. + template + concept __doesnt_need_empty_state_for_copy = copyable<_Tp> || is_nothrow_copy_constructible_v<_Tp>; + + template + concept __doesnt_need_empty_state_for_move = movable<_Tp> || is_nothrow_move_constructible_v<_Tp>; + + template<__copy_constructible_object _Tp> + requires __doesnt_need_empty_state_for_copy<_Tp> && __doesnt_need_empty_state_for_move<_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) + constexpr __copyable_box& operator=(__copyable_box const&) requires copyable<_Tp> = default; + constexpr __copyable_box& operator=(__copyable_box&&) requires movable<_Tp> = default; + + // Implementation of assignment operators in case we perform optimization (2) + constexpr __copyable_box& operator=(__copyable_box const& __other) noexcept { + static_assert(is_nothrow_copy_constructible_v<_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 { + static_assert(is_nothrow_move_constructible_v<_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 bool __has_value() 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,170 @@ +//===----------------------------------------------------------------------===// +// +// 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&) + +// ADDITIONAL_COMPILE_FLAGS: -Wno-self-assign-overloaded + +#include <__ranges/copyable_box.h> + +#include +#include +#include // in_place_t + +#include "test_macros.h" +#include "types.h" + +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.__has_value()); + assert(y.__has_value()); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = x); + + assert(&result == &x); + assert(x.__has_value()); + assert((*x).value == 5); + } + } + + // Test optimization #1 for copy-assignment + { + 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.__has_value()); + assert(y.__has_value()); + assert((*x).value == 10); + assert((*x).did_copy_assign); + } + // check self-assignment (should use the underlying type's assignment too) + { + Box x(std::in_place, 5); + Box& result = (x = x); + + assert(&result == &x); + assert(x.__has_value()); + assert((*x).value == 5); + assert((*x).did_copy_assign); + } + } + + // Test optimization #2 for copy-assignment + { + 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.__has_value()); + assert(y.__has_value()); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = x); + + assert(&result == &x); + assert(x.__has_value()); + assert((*x).value == 5); + } + } + + return true; +} + +// Tests for the empty state. Those can't be constexpr, since they are only reached +// through throwing an exception. +#if !defined(TEST_HAS_NO_EXCEPTIONS) +void test_empty_state() { + 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.__has_value()); + assert(y.__has_value()); + 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.__has_value()); + assert(!y.__has_value()); + } + // assign empty to empty + { + Box x = create_empty_box(); + Box const y = create_empty_box(); + Box& result = (x = y); + + assert(&result == &x); + assert(!x.__has_value()); + assert(!y.__has_value()); + } + // check self-assignment in empty case + { + Box x = create_empty_box(); + Box& result = (x = x); + + assert(&result == &x); + assert(!x.__has_value()); + } +} +#endif // !defined(TEST_HAS_NO_EXCEPTIONS) + +int main(int, char**) { + assert(test()); + static_assert(test()); + +#if !defined(TEST_HAS_NO_EXCEPTIONS) + test_empty_state(); +#endif + + 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,199 @@ +//===----------------------------------------------------------------------===// +// +// 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=(&&) + +// ADDITIONAL_COMPILE_FLAGS: -Wno-self-move + +#include <__ranges/copyable_box.h> + +#include +#include +#include // in_place_t + +#include "test_macros.h" +#include "types.h" + +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.__has_value()); + assert(y.__has_value()); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = std::move(x)); + + assert(&result == &x); + assert(x.__has_value()); + assert((*x).value == 5); + } + } + + // Test optimization #1 for move assignment + { + 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.__has_value()); + assert(y.__has_value()); + assert((*x).value == 10); + assert((*x).did_move_assign); + } + // check self-assignment (should use the underlying type's assignment too) + { + Box x(std::in_place, 5); + Box& result = (x = std::move(x)); + + assert(&result == &x); + assert(x.__has_value()); + assert((*x).value == 5); + assert((*x).did_move_assign); + } + } + + // Test optimization #1 for move assignment with a type that uses optimization #2 for copy assignment + { + using Box = std::ranges::__copyable_box; + static_assert(std::is_move_assignable_v); + static_assert(std::is_nothrow_move_assignable_v == 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.__has_value()); + assert(y.__has_value()); + assert((*x).value == 10); + assert((*x).did_move_assign); + } + // check self-assignment (should use the underlying type's assignment too) + { + Box x(std::in_place, 5); + Box& result = (x = std::move(x)); + + assert(&result == &x); + assert(x.__has_value()); + assert((*x).value == 5); + assert((*x).did_move_assign); + } + } + + // Test optimization #2 for move assignment + { + 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.__has_value()); + assert(y.__has_value()); + assert((*x).value == 10); + } + // check self-assignment + { + Box x(std::in_place, 5); + Box& result = (x = std::move(x)); + + assert(&result == &x); + assert(x.__has_value()); + assert((*x).value == 5); + } + } + + return true; +} + +// Tests for the empty state. Those can't be constexpr, since they are only reached +// through throwing an exception. +#if !defined(TEST_HAS_NO_EXCEPTIONS) +void test_empty_state() { + 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.__has_value()); + assert(y.__has_value()); + 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.__has_value()); + assert(!y.__has_value()); + } + // 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.__has_value()); + assert(!y.__has_value()); + } + // check self-assignment in empty case + { + Box x = create_empty_box(); + Box& result = (x = std::move(x)); + + assert(&result == &x); + assert(!x.__has_value()); + } +} +#endif // !defined(TEST_HAS_NO_EXCEPTIONS) + +int main(int, char**) { + assert(test()); + static_assert(test()); + +#if !defined(TEST_HAS_NO_EXCEPTIONS) + test_empty_state(); +#endif + + 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,66 @@ +//===----------------------------------------------------------------------===// +// +// 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 // in_place_t + +#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); +}; +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.__has_value()); + assert((*box).value == CopyConstructible().value); + } + + // check optimization #1 + { + Box box; + assert(box.__has_value()); + assert((*box).value == Copyable().value); + } + + // check optimization #2 + { + Box box; + assert(box.__has_value()); + 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/ctor.in_place.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/ctor.in_place.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/ctor.in_place.pass.cpp @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// template +// explicit ::(in_place_t, Args&& ...args); + +#include <__ranges/copyable_box.h> + +#include +#include // in_place_t + +#include "types.h" + +struct UnknownType { }; + +template +struct NothrowConstructible { + explicit NothrowConstructible(int) noexcept(Noexcept); +}; + +constexpr bool test() { + // Test the primary template + { + using Box = std::ranges::__copyable_box; + Box x(std::in_place, 5); + assert((*x).value == 5); + + static_assert(!std::is_constructible_v); + } + + // Test optimization #1 + { + using Box = std::ranges::__copyable_box; + Box x(std::in_place, 5); + assert((*x).value == 5); + + static_assert(!std::is_constructible_v); + } + + // Test optimization #2 + { + using Box = std::ranges::__copyable_box; + Box x(std::in_place, 5); + assert((*x).value == 5); + + static_assert(!std::is_constructible_v); + } + + static_assert( std::is_nothrow_constructible_v>, std::in_place_t, int>); + static_assert(!std::is_nothrow_constructible_v>, std::in_place_t, int>); + + 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,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 + +// T& ::operator*() +// T const& ::operator*() const + +#include <__ranges/copyable_box.h> + +#include +#include // in_place_t + +#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/has_value.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/has_value.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/has_value.pass.cpp @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// bool ::__has_value() const + +#include <__ranges/copyable_box.h> + +#include +#include // in_place_t + +#include "types.h" + +template +constexpr void check() { + std::ranges::__copyable_box const x(std::in_place, 10); + assert(x.__has_value()); +} + +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. +#if !defined(TEST_HAS_NO_EXCEPTIONS) + { + std::ranges::__copyable_box x = create_empty_box(); + assert(!x.__has_value()); + } +#endif + + 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,47 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#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(std::optional)); + +// 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,139 @@ +//===----------------------------------------------------------------------===// +// +// 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 <__ranges/copyable_box.h> +#include +#include +#include + +#include "test_macros.h" + +// NOTE: These types are strongly tied to the implementation of __copyable_box. See the documentation +// in __copyable_box for the meaning of optimizations #1 and #2. + +// 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; + CopyConstructible& operator=(CopyConstructible const&) = delete; + + int value = -1; +}; +static_assert(!std::copyable); +static_assert(!std::is_nothrow_copy_constructible_v); +static_assert(!std::movable); +static_assert(!std::is_nothrow_move_constructible_v); + + +// Copyable type that is not nothrow_copy/move_constructible. +// This triggers optimization #1 for the copy assignment and the move assignment. +struct Copyable { + constexpr Copyable() = default; + constexpr explicit Copyable(int x) : value(x) { } + Copyable(Copyable const&) noexcept(false) = default; + + constexpr Copyable& operator=(Copyable const& other) noexcept(false) { + value = other.value; + did_copy_assign = true; + return *this; + } + + constexpr Copyable& operator=(Copyable&& other) noexcept(false) { + value = other.value; + did_move_assign = true; + return *this; + } + + int value = -1; + bool did_copy_assign = false; + bool did_move_assign = false; +}; +static_assert( std::copyable); +static_assert(!std::is_nothrow_copy_constructible_v); +static_assert( std::movable); +static_assert(!std::is_nothrow_move_constructible_v); + + +// Non-copyable type that is nothrow_copy_constructible and nothrow_move_constructible. +// This triggers optimization #2 for the copy assignment and the move assignment. +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; + + int value = -1; +}; +static_assert(!std::copyable); +static_assert( std::is_nothrow_copy_constructible_v); +static_assert(!std::movable); +static_assert( std::is_nothrow_move_constructible_v); + + +// Non-copyable type that is nothrow_copy_constructible, and that is movable but NOT nothrow_move_constructible. +// This triggers optimization #2 for the copy assignment, and optimization #1 for the move assignment. +struct MovableNothrowCopyConstructible { + constexpr MovableNothrowCopyConstructible() = default; + constexpr explicit MovableNothrowCopyConstructible(int x) : value(x) { } + MovableNothrowCopyConstructible(MovableNothrowCopyConstructible const&) noexcept = default; + MovableNothrowCopyConstructible(MovableNothrowCopyConstructible&&) noexcept(false) = default; + constexpr MovableNothrowCopyConstructible& operator=(MovableNothrowCopyConstructible&& other) { + value = other.value; + did_move_assign = true; + return *this; + } + + int value = -1; + bool did_move_assign = false; +}; +static_assert(!std::copyable); +static_assert( std::is_nothrow_copy_constructible_v); +static_assert( std::movable); +static_assert(!std::is_nothrow_move_constructible_v); + + +#if !defined(TEST_HAS_NO_EXCEPTIONS) +// 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.__has_value()); + return box1; + } + assert(false && "should never be reached"); +} +#endif // !defined(TEST_HAS_NO_EXCEPTIONS) + +#endif // TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_COPY_WRAP_TYPES_H