diff --git a/libcxx/include/__string/constexpr_c_functions.h b/libcxx/include/__string/constexpr_c_functions.h --- a/libcxx/include/__string/constexpr_c_functions.h +++ b/libcxx/include/__string/constexpr_c_functions.h @@ -10,15 +10,22 @@ #define _LIBCPP___STRING_CONSTEXPR_C_FUNCTIONS_H #include <__config> +#include <__memory/construct_at.h> #include <__type_traits/datasizeof.h> +#include <__type_traits/integral_constant.h> #include <__type_traits/is_always_bitcastable.h> #include <__type_traits/is_constant_evaluated.h> +#include <__type_traits/is_copy_assignable.h> +#include <__type_traits/is_copy_constructible.h> #include <__type_traits/is_equality_comparable.h> +#include <__type_traits/is_move_assignable.h> +#include <__type_traits/is_move_constructible.h> #include <__type_traits/is_same.h> #include <__type_traits/is_trivially_copyable.h> #include <__type_traits/is_trivially_lexicographically_comparable.h> #include <__type_traits/remove_cv.h> #include <__utility/is_pointer_in_range.h> +#include <__utility/move.h> #include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -133,6 +140,56 @@ } } +// This function performs the assignment of an existing, already alive TriviallyCopyable object. +// It basically works around the fact that TriviallyCopyable objects are not required to be +// syntactically copy/move constructible or copy/move assignable. Technically, only one of the +// four operations is required to be syntactically valid -- but at least one definitely has to +// be valid. +// +// This is necessary in order to implement __constexpr_memmove below in a way that mirrors as +// closely as possible what the compiler's __builtin_memmove is able to do. +template // copy assignment +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Tp& +__assign_trivially_copyable_impl(_Tp* __dest, _Tp&& __src, integral_constant) { + *__dest = __src; + return *__dest; +} + +template // move assignment +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Tp& +__assign_trivially_copyable_impl(_Tp* __dest, _Tp&& __src, integral_constant) { + *__dest = std::move(__src); + return *__dest; +} + +template // copy constructor +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp& +__assign_trivially_copyable_impl(_Tp* __dest, _Tp&& __src, integral_constant) { + std::__construct_at(__dest, __src); + return *__dest; +} + +template // move constructor +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp& +__assign_trivially_copyable_impl(_Tp* __dest, _Tp&& __src, integral_constant) { + std::__construct_at(__dest, std::move(__src)); + return *__dest; +} + +template +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Tp& __assign_trivially_copyable(_Tp* __dest, _Tp&& __src) { + static_assert(is_trivially_copyable<_Tp>::value, + "This function requires the type to be trivially copyable, otherwise we are not guaranteed that " + "we can assign to it using any of the methods used here."); + // clang-format off + return std::__assign_trivially_copyable_impl(__dest, std::move(__src), integral_constant::value ? 0 : + is_move_assignable<_Tp>::value ? 1 : + is_copy_constructible<_Tp>::value ? 2 : + is_move_constructible<_Tp>::value ? 3 : -1>()); + // clang-format on +} + template ::value, int> = 0> _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Tp* __constexpr_memmove(_Tp* __dest, _Up* __src, __element_count __n) { @@ -144,12 +201,17 @@ return __dest; } #endif + // This basically implements __builtin_memmove in a constexpr-friendly way. + // We materialize a _Tp object from each _Up object (which we're allowed to + // because the types are bit-castable), and then we perform an "assignment" + // of the TriviallyCopyable _Tp object without relying on syntax that + // TriviallyCopyable doesn't guarantee we can use. if (std::__is_pointer_in_range(__src, __src + __count, __dest)) { for (; __count > 0; --__count) - __dest[__count - 1] = __src[__count - 1]; + std::__assign_trivially_copyable(__dest + (__count - 1), __builtin_bit_cast(_Tp, __src[__count - 1])); } else { for (size_t __i = 0; __i != __count; ++__i) - __dest[__i] = __src[__i]; + std::__assign_trivially_copyable(__dest + __i, __builtin_bit_cast(_Tp, __src[__i])); } } else if (__count > 0) { ::__builtin_memmove(__dest, __src, (__count - 1) * sizeof(_Tp) + __libcpp_datasizeof<_Tp>::value); diff --git a/libcxx/test/libcxx/strings/c.strings/constexpr_memmove.pass.cpp b/libcxx/test/libcxx/strings/c.strings/constexpr_memmove.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/strings/c.strings/constexpr_memmove.pass.cpp @@ -0,0 +1,137 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// ADDITIONAL_COMPILE_FLAGS: -Wno-private-header + +// _Tp* __constexpr_memmove(_Tp* __dest, _Up* __src, __element_count __n); +// +// General tests for __constexpr_memmove. +// +// In particular, we try to ensure that __constexpr_memmove behaves like +// __builtin_memmove as closely as possible. This means that it produces the +// same effect, but also that it has the same type requirements. +// +// __builtin_memmove only requires that the types are TriviallyCopyable +// (which is interestingly different from both is_trivially_XXX_constructible +// and is_trivially_XXX_assignable), so we use some funky types to test these +// corner cases. + +#include <__string/constexpr_c_functions.h> +#include +#include + +#include "test_macros.h" + +// The following types are all TriviallyCopyable, but they are not all +// trivially_{copy,move}_{constructible,assignable}. TriviallyCopyable +// guarantees that the type is *at least* one of the four, but no more +// than that. +struct CopyConstructible { + CopyConstructible() = default; + int value = 0; + + CopyConstructible(const CopyConstructible&) = default; + CopyConstructible(CopyConstructible&&) = delete; + CopyConstructible& operator=(const CopyConstructible&) = delete; + CopyConstructible& operator=(CopyConstructible&&) = delete; +}; + +struct MoveConstructible { + MoveConstructible() = default; + int value = 0; + + MoveConstructible(const MoveConstructible&) = delete; + MoveConstructible(MoveConstructible&&) = default; + MoveConstructible& operator=(const MoveConstructible&) = delete; + MoveConstructible& operator=(MoveConstructible&&) = delete; +}; + +struct CopyAssignable { + CopyAssignable() = default; + int value = 0; + + CopyAssignable(const CopyAssignable&) = delete; + CopyAssignable(CopyAssignable&&) = delete; + CopyAssignable& operator=(const CopyAssignable&) = default; + CopyAssignable& operator=(CopyAssignable&&) = delete; +}; + +struct MoveAssignable { + MoveAssignable() = default; + int value = 0; + + MoveAssignable(const MoveAssignable&) = delete; + MoveAssignable(MoveAssignable&&) = delete; + MoveAssignable& operator=(const MoveAssignable&) = delete; + MoveAssignable& operator=(MoveAssignable&&) = default; +}; + +template +TEST_CONSTEXPR_CXX14 void test_user_defined_types() { + static_assert(std::is_trivially_copyable::value, "test the test"); + static_assert(std::is_trivially_copyable::value, "test the test"); + + // Note that we can't just initialize with an initializer list since some of the types we use here + // are not copy-constructible, which is required in pre-C++20 Standards for that syntax to work. + Source src[3]; + src[0].value = 1; + src[1].value = 2; + src[2].value = 3; + Dest dst[3]; + dst[0].value = 111; + dst[1].value = 111; + dst[2].value = 111; + + Dest* result = std::__constexpr_memmove(dst, src, std::__element_count(3)); + assert(result == dst); + assert(dst[0].value == 1); + assert(dst[1].value == 2); + assert(dst[2].value == 3); +} + +template +TEST_CONSTEXPR_CXX14 void test_builtin_types() { + Source src[3] = {1, 2, 3}; + Dest dst[3] = {111, 111, 111}; + + Dest* result = std::__constexpr_memmove(dst, src, std::__element_count(3)); + assert(result == dst); + assert(dst[0] == 1); + assert(dst[1] == 2); + assert(dst[2] == 3); +} + +TEST_CONSTEXPR_CXX14 bool test() { + test_user_defined_types(); + test_user_defined_types(); + test_user_defined_types(); + test_user_defined_types(); + + test_builtin_types(); + test_builtin_types(); + test_builtin_types(); + test_builtin_types(); + test_builtin_types(); + + // Cross-type + test_builtin_types(); + test_builtin_types(); + test_builtin_types(); + test_builtin_types(); + + return true; +} + +int main(int, char**) { + test(); +#if TEST_STD_VER >= 14 + static_assert(test(), ""); +#endif + return 0; +} diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/move.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/move.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/move.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/move.pass.cpp @@ -18,10 +18,12 @@ #include #include +#include #include -#include "test_macros.h" +#include "MoveOnly.h" #include "test_iterators.h" +#include "test_macros.h" class PaddedBase { public: @@ -111,11 +113,32 @@ assert(std::equal(a, a + 10, expected)); } + // Make sure that the algorithm works with move-only types + { + // When non-trivial + { + MoveOnly from[3] = {1, 2, 3}; + MoveOnly to[3] = {}; + std::move(std::begin(from), std::end(from), std::begin(to)); + assert(to[0] == MoveOnly(1)); + assert(to[1] == MoveOnly(2)); + assert(to[2] == MoveOnly(3)); + } + // When trivial + { + TrivialMoveOnly from[3] = {1, 2, 3}; + TrivialMoveOnly to[3] = {}; + std::move(std::begin(from), std::end(from), std::begin(to)); + assert(to[0] == TrivialMoveOnly(1)); + assert(to[1] == TrivialMoveOnly(2)); + assert(to[2] == TrivialMoveOnly(3)); + } + } + return true; } -int main(int, char**) -{ +int main(int, char**) { test(); #if TEST_STD_VER >= 20 static_assert(test()); diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/move_backward.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/move_backward.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/move_backward.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/move_backward.pass.cpp @@ -17,10 +17,12 @@ #include #include +#include #include -#include "test_macros.h" +#include "MoveOnly.h" #include "test_iterators.h" +#include "test_macros.h" class PaddedBase { public: @@ -110,6 +112,28 @@ assert(std::equal(a, a + 10, expected)); } + // Make sure that the algorithm works with move-only types + { + // When non-trivial + { + MoveOnly from[3] = {1, 2, 3}; + MoveOnly to[3] = {}; + std::move_backward(std::begin(from), std::end(from), std::end(to)); + assert(to[0] == MoveOnly(1)); + assert(to[1] == MoveOnly(2)); + assert(to[2] == MoveOnly(3)); + } + // When trivial + { + TrivialMoveOnly from[3] = {1, 2, 3}; + TrivialMoveOnly to[3] = {}; + std::move_backward(std::begin(from), std::end(from), std::end(to)); + assert(to[0] == TrivialMoveOnly(1)); + assert(to[1] == TrivialMoveOnly(2)); + assert(to[2] == TrivialMoveOnly(3)); + } + } + return true; } diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -203,6 +204,7 @@ test_proxy_in_iterators(); { // check that a move-only type works + // When non-trivial { MoveOnly a[] = {1, 2, 3}; MoveOnly b[3]; @@ -219,6 +221,24 @@ assert(b[1].get() == 2); assert(b[2].get() == 3); } + + // When trivial + { + TrivialMoveOnly a[] = {1, 2, 3}; + TrivialMoveOnly b[3]; + std::ranges::move(a, std::begin(b)); + assert(b[0].get() == 1); + assert(b[1].get() == 2); + assert(b[2].get() == 3); + } + { + TrivialMoveOnly a[] = {1, 2, 3}; + TrivialMoveOnly b[3]; + std::ranges::move(std::begin(a), std::end(a), std::begin(b)); + assert(b[0].get() == 1); + assert(b[1].get() == 2); + assert(b[2].get() == 3); + } } { // check that a move-only type works for ProxyIterator diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.pass.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -189,6 +190,7 @@ test_proxy_in_iterators(); { // check that a move-only type works + // When non-trivial { MoveOnly a[] = {1, 2, 3}; MoveOnly b[3]; @@ -205,6 +207,24 @@ assert(b[1].get() == 2); assert(b[2].get() == 3); } + + // When trivial + { + TrivialMoveOnly a[] = {1, 2, 3}; + TrivialMoveOnly b[3]; + std::ranges::move_backward(a, std::end(b)); + assert(b[0].get() == 1); + assert(b[1].get() == 2); + assert(b[2].get() == 3); + } + { + TrivialMoveOnly a[] = {1, 2, 3}; + TrivialMoveOnly b[3]; + std::ranges::move_backward(std::begin(a), std::end(a), std::end(b)); + assert(b[0].get() == 1); + assert(b[1].get() == 2); + assert(b[2].get() == 3); + } } { // check that a move-only type works for ProxyIterator diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp --- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp +++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter.pass.cpp @@ -14,9 +14,10 @@ #include #include -#include "test_macros.h" -#include "min_allocator.h" #include "asan_testing.h" +#include "min_allocator.h" +#include "MoveOnly.h" +#include "test_macros.h" #ifndef TEST_HAS_NO_EXCEPTIONS struct Throws { @@ -28,7 +29,7 @@ Throws& operator=( Throws &&rhs) { v_ = rhs.v_; return *this; } int v_; static bool sThrows; - }; +}; bool Throws::sThrows = false; #endif @@ -36,70 +37,92 @@ TEST_CONSTEXPR_CXX20 bool tests() { { - int a1[] = {1, 2, 3, 4, 5}; - std::vector l1(a1, a1+5); - l1.erase(l1.begin()); - assert(is_contiguous_container_asan_correct(l1)); - assert(l1 == std::vector(a1+1, a1+5)); + int a1[] = {1, 2, 3, 4, 5}; + std::vector l1(a1, a1+5); + l1.erase(l1.begin()); + assert(is_contiguous_container_asan_correct(l1)); + assert(l1 == std::vector(a1+1, a1+5)); } { - int a1[] = {1, 2, 3, 4, 5}; - int e1[] = {1, 3, 4, 5}; - std::vector l1(a1, a1+5); - l1.erase(l1.begin() + 1); - assert(is_contiguous_container_asan_correct(l1)); - assert(l1 == std::vector(e1, e1+4)); + int a1[] = {1, 2, 3, 4, 5}; + int e1[] = {1, 3, 4, 5}; + std::vector l1(a1, a1+5); + l1.erase(l1.begin() + 1); + assert(is_contiguous_container_asan_correct(l1)); + assert(l1 == std::vector(e1, e1+4)); } { - int a1[] = {1, 2, 3}; - std::vector l1(a1, a1+3); - std::vector::const_iterator i = l1.begin(); - assert(is_contiguous_container_asan_correct(l1)); - ++i; - std::vector::iterator j = l1.erase(i); - assert(l1.size() == 2); - assert(std::distance(l1.begin(), l1.end()) == 2); - assert(*j == 3); - assert(*l1.begin() == 1); - assert(*std::next(l1.begin()) == 3); - assert(is_contiguous_container_asan_correct(l1)); - j = l1.erase(j); - assert(j == l1.end()); - assert(l1.size() == 1); - assert(std::distance(l1.begin(), l1.end()) == 1); - assert(*l1.begin() == 1); - assert(is_contiguous_container_asan_correct(l1)); - j = l1.erase(l1.begin()); - assert(j == l1.end()); - assert(l1.size() == 0); - assert(std::distance(l1.begin(), l1.end()) == 0); - assert(is_contiguous_container_asan_correct(l1)); + int a1[] = {1, 2, 3}; + std::vector l1(a1, a1+3); + std::vector::const_iterator i = l1.begin(); + assert(is_contiguous_container_asan_correct(l1)); + ++i; + std::vector::iterator j = l1.erase(i); + assert(l1.size() == 2); + assert(std::distance(l1.begin(), l1.end()) == 2); + assert(*j == 3); + assert(*l1.begin() == 1); + assert(*std::next(l1.begin()) == 3); + assert(is_contiguous_container_asan_correct(l1)); + j = l1.erase(j); + assert(j == l1.end()); + assert(l1.size() == 1); + assert(std::distance(l1.begin(), l1.end()) == 1); + assert(*l1.begin() == 1); + assert(is_contiguous_container_asan_correct(l1)); + j = l1.erase(l1.begin()); + assert(j == l1.end()); + assert(l1.size() == 0); + assert(std::distance(l1.begin(), l1.end()) == 0); + assert(is_contiguous_container_asan_correct(l1)); } + // Make sure vector::erase works with move-only types + { + // When non-trivial + { + std::vector v; + v.emplace_back(1); v.emplace_back(2); v.emplace_back(3); + v.erase(v.begin()); + assert(v.size() == 2); + assert(v[0] == MoveOnly(2)); + assert(v[1] == MoveOnly(3)); + } + // When trivial + { + std::vector v; + v.emplace_back(1); v.emplace_back(2); v.emplace_back(3); + v.erase(v.begin()); + assert(v.size() == 2); + assert(v[0] == TrivialMoveOnly(2)); + assert(v[1] == TrivialMoveOnly(3)); + } + } + #if TEST_STD_VER >= 11 { - int a1[] = {1, 2, 3}; - std::vector> l1(a1, a1+3); - std::vector>::const_iterator i = l1.begin(); - assert(is_contiguous_container_asan_correct(l1)); - ++i; - std::vector>::iterator j = l1.erase(i); - assert(l1.size() == 2); - assert(std::distance(l1.begin(), l1.end()) == 2); - assert(*j == 3); - assert(*l1.begin() == 1); - assert(*std::next(l1.begin()) == 3); - assert(is_contiguous_container_asan_correct(l1)); - j = l1.erase(j); - assert(j == l1.end()); - assert(l1.size() == 1); - assert(std::distance(l1.begin(), l1.end()) == 1); - assert(*l1.begin() == 1); - assert(is_contiguous_container_asan_correct(l1)); - j = l1.erase(l1.begin()); - assert(j == l1.end()); - assert(l1.size() == 0); - assert(std::distance(l1.begin(), l1.end()) == 0); - assert(is_contiguous_container_asan_correct(l1)); + int a1[] = {1, 2, 3}; + std::vector> l1(a1, a1+3); + std::vector>::const_iterator i = l1.begin(); + assert(is_contiguous_container_asan_correct(l1)); + ++i; + std::vector>::iterator j = l1.erase(i); + assert(l1.size() == 2); + assert(std::distance(l1.begin(), l1.end()) == 2); + assert(*j == 3); + assert(*l1.begin() == 1); + assert(*std::next(l1.begin()) == 3); + assert(is_contiguous_container_asan_correct(l1)); + j = l1.erase(j); + assert(j == l1.end()); + assert(l1.size() == 1); + assert(std::distance(l1.begin(), l1.end()) == 1); + assert(*l1.begin() == 1); + assert(is_contiguous_container_asan_correct(l1)); + j = l1.erase(l1.begin()); + assert(j == l1.end()); + assert(l1.size() == 0); + assert(std::distance(l1.begin(), l1.end()) == 0); + assert(is_contiguous_container_asan_correct(l1)); } #endif diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp --- a/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp +++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/erase_iter_iter.pass.cpp @@ -14,9 +14,10 @@ #include #include -#include "test_macros.h" -#include "min_allocator.h" #include "asan_testing.h" +#include "min_allocator.h" +#include "MoveOnly.h" +#include "test_macros.h" #ifndef TEST_HAS_NO_EXCEPTIONS struct Throws { @@ -87,6 +88,25 @@ assert(is_contiguous_container_asan_correct(outer[0])); assert(is_contiguous_container_asan_correct(outer[1])); } + // Make sure vector::erase works with move-only types + { + // When non-trivial + { + std::vector v; + v.emplace_back(1); v.emplace_back(2); v.emplace_back(3); + v.erase(v.begin(), v.begin() + 2); + assert(v.size() == 1); + assert(v[0] == MoveOnly(3)); + } + // When trivial + { + std::vector v; + v.emplace_back(1); v.emplace_back(2); v.emplace_back(3); + v.erase(v.begin(), v.begin() + 2); + assert(v.size() == 1); + assert(v[0] == TrivialMoveOnly(3)); + } + } #if TEST_STD_VER >= 11 { std::vector> l1(a1, a1+3); diff --git a/libcxx/test/support/MoveOnly.h b/libcxx/test/support/MoveOnly.h --- a/libcxx/test/support/MoveOnly.h +++ b/libcxx/test/support/MoveOnly.h @@ -56,7 +56,6 @@ friend void operator,(T t, U u) = delete; }; - template <> struct std::hash { @@ -65,4 +64,59 @@ TEST_CONSTEXPR std::size_t operator()(const MoveOnly& x) const {return static_cast(x.get());} }; +class TrivialMoveOnly { + int data_; + + public: + TEST_CONSTEXPR TrivialMoveOnly(int data = 1) : data_(data) {} + + TrivialMoveOnly(const TrivialMoveOnly&) = delete; + TrivialMoveOnly& operator=(const TrivialMoveOnly&) = delete; + + TrivialMoveOnly(TrivialMoveOnly&&) = default; + TrivialMoveOnly& operator=(TrivialMoveOnly&&) = default; + + TEST_CONSTEXPR int get() const { return data_; } + + friend TEST_CONSTEXPR bool operator==(const TrivialMoveOnly& x, const TrivialMoveOnly& y) { + return x.data_ == y.data_; + } + friend TEST_CONSTEXPR bool operator!=(const TrivialMoveOnly& x, const TrivialMoveOnly& y) { + return x.data_ != y.data_; + } + friend TEST_CONSTEXPR bool operator<(const TrivialMoveOnly& x, const TrivialMoveOnly& y) { + return x.data_ < y.data_; + } + friend TEST_CONSTEXPR bool operator<=(const TrivialMoveOnly& x, const TrivialMoveOnly& y) { + return x.data_ <= y.data_; + } + friend TEST_CONSTEXPR bool operator>(const TrivialMoveOnly& x, const TrivialMoveOnly& y) { + return x.data_ > y.data_; + } + friend TEST_CONSTEXPR bool operator>=(const TrivialMoveOnly& x, const TrivialMoveOnly& y) { + return x.data_ >= y.data_; + } + +#if TEST_STD_VER > 17 + friend constexpr auto operator<=>(const TrivialMoveOnly&, const TrivialMoveOnly&) = default; +#endif // TEST_STD_VER > 17 + + TEST_CONSTEXPR_CXX14 TrivialMoveOnly operator+(const TrivialMoveOnly& x) const { + return TrivialMoveOnly(data_ + x.data_); + } + TEST_CONSTEXPR_CXX14 TrivialMoveOnly operator*(const TrivialMoveOnly& x) const { + return TrivialMoveOnly(data_ * x.data_); + } + + template + friend void operator,(T t, U u) = delete; +}; + +template <> +struct std::hash { + typedef TrivialMoveOnly argument_type; + typedef std::size_t result_type; + TEST_CONSTEXPR std::size_t operator()(const TrivialMoveOnly& x) const { return static_cast(x.get()); } +}; + #endif // MOVEONLY_H