[libcxx-commits] [libcxx] 6829db7 - [libc++] Implement copyable-box from Ranges
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Jul 7 03:14:46 PDT 2021
Author: Louis Dionne
Date: 2021-07-07T06:14:24-04:00
New Revision: 6829db727e9e67dfdb70dd0846ffd4e48e00a98d
URL: https://github.com/llvm/llvm-project/commit/6829db727e9e67dfdb70dd0846ffd4e48e00a98d
DIFF: https://github.com/llvm/llvm-project/commit/6829db727e9e67dfdb70dd0846ffd4e48e00a98d.diff
LOG: [libc++] Implement copyable-box from Ranges
Differential Revision: https://reviews.llvm.org/D102135
Added:
libcxx/include/__ranges/copyable_box.h
libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/assign.copy.pass.cpp
libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/assign.move.pass.cpp
libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/ctor.default.pass.cpp
libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/ctor.in_place.pass.cpp
libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/deref.pass.cpp
libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/has_value.pass.cpp
libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/no_unique_address.pass.cpp
libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/properties.compile.pass.cpp
libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/types.h
Modified:
libcxx/include/CMakeLists.txt
libcxx/include/module.modulemap
Removed:
################################################################################
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index f05473bb4348..a1d5374c30a9 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -184,6 +184,7 @@ set(files
__ranges/access.h
__ranges/all.h
__ranges/concepts.h
+ __ranges/copyable_box.h
__ranges/data.h
__ranges/drop_view.h
__ranges/empty_view.h
diff --git a/libcxx/include/__ranges/copyable_box.h b/libcxx/include/__ranges/copyable_box.h
new file mode 100644
index 000000000000..f2d3843f79f5
--- /dev/null
+++ b/libcxx/include/__ranges/copyable_box.h
@@ -0,0 +1,175 @@
+// -*- 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 <concepts>
+#include <optional>
+#include <type_traits>
+
+#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<class _Tp>
+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 {
+ [[no_unique_address]] optional<_Tp> __val_;
+
+ public:
+ template<class ..._Args>
+ requires is_constructible_v<_Tp, _Args...>
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr explicit __copyable_box(in_place_t, _Args&& ...__args)
+ noexcept(is_nothrow_constructible_v<_Tp, _Args...>)
+ : __val_(in_place, _VSTD::forward<_Args>(__args)...)
+ { }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __copyable_box() noexcept(is_nothrow_default_constructible_v<_Tp>)
+ requires default_initializable<_Tp>
+ : __val_(in_place)
+ { }
+
+ _LIBCPP_HIDE_FROM_ABI __copyable_box(__copyable_box const&) = default;
+ _LIBCPP_HIDE_FROM_ABI __copyable_box(__copyable_box&&) = default;
+
+ _LIBCPP_HIDE_FROM_ABI
+ 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;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ __copyable_box& operator=(__copyable_box&&) requires movable<_Tp> = default;
+
+ _LIBCPP_HIDE_FROM_ABI
+ 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;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr _Tp const& operator*() const noexcept { return *__val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() noexcept { return *__val_; }
+ _LIBCPP_HIDE_FROM_ABI 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<class _Tp>
+ concept __doesnt_need_empty_state_for_copy = copyable<_Tp> || is_nothrow_copy_constructible_v<_Tp>;
+
+ template<class _Tp>
+ 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<class ..._Args>
+ requires is_constructible_v<_Tp, _Args...>
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr explicit __copyable_box(in_place_t, _Args&& ...__args)
+ noexcept(is_nothrow_constructible_v<_Tp, _Args...>)
+ : __val_(_VSTD::forward<_Args>(__args)...)
+ { }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __copyable_box() noexcept(is_nothrow_default_constructible_v<_Tp>)
+ requires default_initializable<_Tp>
+ : __val_()
+ { }
+
+ _LIBCPP_HIDE_FROM_ABI __copyable_box(__copyable_box const&) = default;
+ _LIBCPP_HIDE_FROM_ABI __copyable_box(__copyable_box&&) = default;
+
+ // Implementation of assignment operators in case we perform optimization (1)
+ _LIBCPP_HIDE_FROM_ABI __copyable_box& operator=(__copyable_box const&) requires copyable<_Tp> = default;
+ _LIBCPP_HIDE_FROM_ABI __copyable_box& operator=(__copyable_box&&) requires movable<_Tp> = default;
+
+ // Implementation of assignment operators in case we perform optimization (2)
+ _LIBCPP_HIDE_FROM_ABI
+ 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(_VSTD::addressof(__val_));
+ _VSTD::construct_at(_VSTD::addressof(__val_), __other.__val_);
+ }
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr __copyable_box& operator=(__copyable_box&& __other) noexcept {
+ static_assert(is_nothrow_move_constructible_v<_Tp>);
+ if (this != _VSTD::addressof(__other)) {
+ _VSTD::destroy_at(_VSTD::addressof(__val_));
+ _VSTD::construct_at(_VSTD::addressof(__val_), _VSTD::move(__other.__val_));
+ }
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr _Tp const& operator*() const noexcept { return __val_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() noexcept { return __val_; }
+ _LIBCPP_HIDE_FROM_ABI 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
index 0e2261d561b5..eb4488b6342a 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -600,6 +600,7 @@ module std [system] {
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
index 000000000000..91ea226cc40d
--- /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
+
+// <copyable-box>& operator=(<copyable-box> const&)
+
+// ADDITIONAL_COMPILE_FLAGS: -Wno-self-assign-overloaded
+
+#include <__ranges/copyable_box.h>
+
+#include <cassert>
+#include <type_traits>
+#include <utility> // in_place_t
+
+#include "test_macros.h"
+#include "types.h"
+
+constexpr bool test() {
+ // Test the primary template
+ {
+ using Box = std::ranges::__copyable_box<CopyConstructible>;
+ static_assert( std::is_copy_assignable_v<Box>);
+ static_assert(!std::is_nothrow_copy_assignable_v<Box>);
+
+ {
+ 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<Copyable>;
+ static_assert( std::is_copy_assignable_v<Box>);
+ static_assert(!std::is_nothrow_copy_assignable_v<Box>);
+
+ {
+ 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<NothrowCopyConstructible>;
+ static_assert(std::is_copy_assignable_v<Box>);
+ static_assert(std::is_nothrow_copy_assignable_v<Box>);
+
+ {
+ 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<ThrowsOnCopy>;
+
+ // 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
index 000000000000..7b2e1bd111bd
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/assign.move.pass.cpp
@@ -0,0 +1,228 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <copyable-box>& operator=(<copyable-box>&&)
+
+// ADDITIONAL_COMPILE_FLAGS: -Wno-self-move
+
+#include <__ranges/copyable_box.h>
+
+#include <cassert>
+#include <type_traits>
+#include <utility> // in_place_t
+
+#include "test_macros.h"
+#include "types.h"
+
+constexpr bool test() {
+ // Test the primary template
+ {
+ using Box = std::ranges::__copyable_box<CopyConstructible>;
+ static_assert( std::is_move_assignable_v<Box>);
+ static_assert(!std::is_nothrow_move_assignable_v<Box>);
+
+ {
+ 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);
+ }
+ }
+
+ // Make sure that we use the native move assignment in the primary template if we can.
+ {
+ using Box = std::ranges::__copyable_box<CopyConstructibleMovable>;
+ static_assert(std::is_move_assignable_v<Box>);
+ static_assert(std::is_nothrow_move_assignable_v<Box> == std::is_nothrow_move_assignable_v<CopyConstructibleMovable>);
+
+ {
+ 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
+ {
+ 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
+ {
+ using Box = std::ranges::__copyable_box<Copyable>;
+ static_assert( std::is_move_assignable_v<Box>);
+ static_assert(!std::is_nothrow_move_assignable_v<Box>);
+
+ {
+ 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<MovableNothrowCopyConstructible>;
+ static_assert(std::is_move_assignable_v<Box>);
+ static_assert(std::is_nothrow_move_assignable_v<Box> == std::is_nothrow_move_assignable_v<MovableNothrowCopyConstructible>);
+
+ {
+ 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<NothrowCopyConstructible>;
+ static_assert(std::is_move_assignable_v<Box>);
+ static_assert(std::is_nothrow_move_assignable_v<Box>);
+
+ {
+ 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<ThrowsOnCopy>;
+
+ // 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
index 000000000000..324394f81ae6
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/ctor.default.pass.cpp
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <copyable-box>::<copyable-box>()
+
+#include <__ranges/copyable_box.h>
+
+#include <cassert>
+#include <type_traits>
+#include <utility> // in_place_t
+
+#include "types.h"
+
+template<class T>
+using Box = std::ranges::__copyable_box<T>;
+
+struct NoDefault {
+ NoDefault() = delete;
+};
+static_assert(!std::is_default_constructible_v<Box<NoDefault>>);
+
+template<bool Noexcept>
+struct DefaultNoexcept {
+ DefaultNoexcept() noexcept(Noexcept);
+};
+static_assert( std::is_nothrow_default_constructible_v<Box<DefaultNoexcept<true>>>);
+static_assert(!std::is_nothrow_default_constructible_v<Box<DefaultNoexcept<false>>>);
+
+constexpr bool test() {
+ // check primary template
+ {
+ Box<CopyConstructible> box;
+ assert(box.__has_value());
+ assert((*box).value == CopyConstructible().value);
+ }
+
+ // check optimization #1
+ {
+ Box<Copyable> box;
+ assert(box.__has_value());
+ assert((*box).value == Copyable().value);
+ }
+
+ // check optimization #2
+ {
+ Box<NothrowCopyConstructible> 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
index 000000000000..decbf0430af1
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/ctor.in_place.pass.cpp
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// 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<class ...Args>
+// explicit <copyable-box>::<copyable-box>(in_place_t, Args&& ...args);
+
+#include <__ranges/copyable_box.h>
+
+#include <cassert>
+#include <type_traits>
+#include <utility> // in_place_t
+
+#include "types.h"
+
+struct UnknownType { };
+
+template<bool Noexcept>
+struct NothrowConstructible {
+ explicit NothrowConstructible(int) noexcept(Noexcept);
+};
+
+constexpr bool test() {
+ // Test the primary template
+ {
+ using Box = std::ranges::__copyable_box<CopyConstructible>;
+ Box x(std::in_place, 5);
+ assert((*x).value == 5);
+
+ static_assert(!std::is_constructible_v<Box, std::in_place_t, UnknownType>);
+ }
+
+ // Test optimization #1
+ {
+ using Box = std::ranges::__copyable_box<Copyable>;
+ Box x(std::in_place, 5);
+ assert((*x).value == 5);
+
+ static_assert(!std::is_constructible_v<Box, std::in_place_t, UnknownType>);
+ }
+
+ // Test optimization #2
+ {
+ using Box = std::ranges::__copyable_box<NothrowCopyConstructible>;
+ Box x(std::in_place, 5);
+ assert((*x).value == 5);
+
+ static_assert(!std::is_constructible_v<Box, std::in_place_t, UnknownType>);
+ }
+
+ static_assert( std::is_nothrow_constructible_v<std::ranges::__copyable_box<NothrowConstructible<true>>, std::in_place_t, int>);
+ static_assert(!std::is_nothrow_constructible_v<std::ranges::__copyable_box<NothrowConstructible<false>>, 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
index 000000000000..4582effeff0a
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/deref.pass.cpp
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// 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& <copyable-box>::operator*()
+// T const& <copyable-box>::operator*() const
+
+#include <__ranges/copyable_box.h>
+
+#include <cassert>
+#include <type_traits>
+#include <utility> // in_place_t
+
+#include "types.h"
+
+template<class T>
+constexpr void check() {
+ // non-const version
+ {
+ std::ranges::__copyable_box<T> x(std::in_place, 10);
+ T& result = *x;
+ static_assert(noexcept(*x));
+ assert(result.value == 10);
+ }
+
+ // const version
+ {
+ std::ranges::__copyable_box<T> const x(std::in_place, 10);
+ T const& result = *x;
+ static_assert(noexcept(*x));
+ assert(result.value == 10);
+ }
+}
+
+constexpr bool test() {
+ check<CopyConstructible>(); // primary template
+ check<Copyable>(); // optimization #1
+ check<NothrowCopyConstructible>(); // 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
index 000000000000..e1bd6287efc9
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/has_value.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
+
+// bool <copyable-box>::__has_value() const
+
+#include <__ranges/copyable_box.h>
+
+#include <cassert>
+#include <type_traits>
+#include <utility> // in_place_t
+
+#include "types.h"
+
+template<class T>
+constexpr void check() {
+ std::ranges::__copyable_box<T> const x(std::in_place, 10);
+ assert(x.__has_value());
+}
+
+constexpr bool test() {
+ check<CopyConstructible>(); // primary template
+ check<Copyable>(); // optimization #1
+ check<NothrowCopyConstructible>(); // 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<ThrowsOnCopy> x = create_empty_box();
+ assert(!x.__has_value());
+ }
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/no_unique_address.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/no_unique_address.pass.cpp
new file mode 100644
index 000000000000..4b094a703fb6
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/no_unique_address.pass.cpp
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// This test ensures that <copyable-box> behaves correctly when it holds an empty type.
+
+#include <__ranges/copyable_box.h>
+
+#include <cassert>
+#include <utility>
+
+bool copied = false;
+bool moved = false;
+
+struct Empty {
+ Empty() noexcept { }
+ Empty(Empty const&) noexcept { copied = true; }
+ Empty(Empty&&) noexcept { moved = true; }
+ Empty& operator=(Empty const&) = delete;
+ Empty& operator=(Empty&&) = delete;
+};
+
+using Box = std::ranges::__copyable_box<Empty>;
+
+struct Inherit : Box { };
+
+struct Hold : Box {
+ [[no_unique_address]] Inherit member;
+};
+
+int main(int, char**) {
+ Hold box;
+
+ Box& base = static_cast<Box&>(box);
+ Box& member = static_cast<Box&>(box.member);
+
+ // Despite [[no_unique_address]], the two objects have the same type so they
+ // can't share the same address.
+ assert(&base != &member);
+
+ // Make sure that we do perform the copy-construction, which wouldn't be the
+ // case if the two <copyable-box>s had the same address.
+ base = member;
+ assert(copied);
+
+ base = std::move(member);
+ assert(moved);
+
+ 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
index 000000000000..5e933e84d680
--- /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 <copyable-box>
+
+#include <__ranges/copyable_box.h>
+
+#include <optional>
+
+#include "types.h"
+
+template <class T>
+constexpr bool valid_copyable_box = requires {
+ typename std::ranges::__copyable_box<T>;
+};
+
+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<void>); // not an object type
+static_assert(!valid_copyable_box<int&>); // not an object type
+static_assert(!valid_copyable_box<NotCopyConstructible>);
+
+// primary template
+static_assert(sizeof(std::ranges::__copyable_box<CopyConstructible>) == sizeof(std::optional<CopyConstructible>));
+
+// optimization #1
+static_assert(sizeof(std::ranges::__copyable_box<Copyable>) == sizeof(Copyable));
+static_assert(alignof(std::ranges::__copyable_box<Copyable>) == alignof(Copyable));
+
+// optimization #2
+static_assert(sizeof(std::ranges::__copyable_box<NothrowCopyConstructible>) == sizeof(NothrowCopyConstructible));
+static_assert(alignof(std::ranges::__copyable_box<NothrowCopyConstructible>) == 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
index 000000000000..556023f50536
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.copy.wrap/types.h
@@ -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
+//
+//===----------------------------------------------------------------------===//
+
+#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 <cassert>
+#include <concepts>
+#include <type_traits>
+
+#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<CopyConstructible>);
+static_assert(!std::is_nothrow_copy_constructible_v<CopyConstructible>);
+static_assert(!std::movable<CopyConstructible>);
+static_assert(!std::is_nothrow_move_constructible_v<CopyConstructible>);
+
+
+// Copy constructible and movable, but not copyable. This uses the primary template, however we're
+// still able to use the native move-assignment operator in this case.
+struct CopyConstructibleMovable {
+ constexpr CopyConstructibleMovable() = default;
+ constexpr explicit CopyConstructibleMovable(int x) : value(x) { }
+ CopyConstructibleMovable(CopyConstructibleMovable const&) noexcept(false) = default;
+ CopyConstructibleMovable(CopyConstructibleMovable&&) noexcept(false) = default;
+ CopyConstructibleMovable& operator=(CopyConstructibleMovable const&) = delete;
+
+ constexpr CopyConstructibleMovable& operator=(CopyConstructibleMovable&& other) {
+ value = other.value;
+ did_move_assign = true;
+ return *this;
+ }
+
+ int value = -1;
+ bool did_move_assign = false;
+};
+
+
+// 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<Copyable>);
+static_assert(!std::is_nothrow_copy_constructible_v<Copyable>);
+static_assert( std::movable<Copyable>);
+static_assert(!std::is_nothrow_move_constructible_v<Copyable>);
+
+
+// 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<NothrowCopyConstructible>);
+static_assert( std::is_nothrow_copy_constructible_v<NothrowCopyConstructible>);
+static_assert(!std::movable<NothrowCopyConstructible>);
+static_assert( std::is_nothrow_move_constructible_v<NothrowCopyConstructible>);
+
+
+// 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<MovableNothrowCopyConstructible>);
+static_assert( std::is_nothrow_copy_constructible_v<MovableNothrowCopyConstructible>);
+static_assert( std::movable<MovableNothrowCopyConstructible>);
+static_assert(!std::is_nothrow_move_constructible_v<MovableNothrowCopyConstructible>);
+
+
+#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<ThrowsOnCopy> create_empty_box() {
+ std::ranges::__copyable_box<ThrowsOnCopy> box1;
+ std::ranges::__copyable_box<ThrowsOnCopy> 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");
+ return box1; // to silence warning about missing return in non-void function
+}
+#endif // !defined(TEST_HAS_NO_EXCEPTIONS)
+
+#endif // TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_COPY_WRAP_TYPES_H
More information about the libcxx-commits
mailing list