[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