diff --git a/libcxx/include/concepts b/libcxx/include/concepts --- a/libcxx/include/concepts +++ b/libcxx/include/concepts @@ -219,6 +219,8 @@ { __lhs = _VSTD::forward<_Rhs>(__rhs) } -> same_as<_Lhs>; }; +// [concept.swappable] under [concept.moveconstructible] + // [concept.destructible] template @@ -243,6 +245,79 @@ concept move_constructible = constructible_from<_Tp, _Tp> && convertible_to<_Tp, _Tp>; +// [concept.swappable] +namespace ranges::__swap { + // Deleted to inhibit ADL + template + void swap(_Tp&, _Tp&) = delete; + + template + concept __class_or_enum = is_class_v<_Tp> || is_enum_v<_Tp>; + + // [1] + template + concept __unqualified_swappable_with = + (__class_or_enum> || __class_or_enum>) && + requires(_Tp&& __t, _Up&& __u) { + swap(_VSTD::forward<_Tp>(__t), _VSTD::forward<_Up>(__u)); + }; + + struct __fn; + + template + concept __swappable_arrays = + !__unqualified_swappable_with<_Tp(&)[_Size], _Up(&)[_Size]> && + extent_v<_Tp> == extent_v<_Up> && + requires(_Tp(& __t)[_Size], _Up(& __u)[_Size], const __fn& __swap) { + __swap(__t[0], __u[0]); + }; + + template + concept __exchangeable = + !__unqualified_swappable_with<_Tp&, _Tp&> && + move_constructible<_Tp> && + assignable_from<_Tp&, _Tp>; + + struct __fn { + // 2.1 `S` is `(void)swap(E1, E2)`* if `E1` or `E2` has class or enumeration type and... + // *The name `swap` is used here unqualified. + template + requires __unqualified_swappable_with<_Tp, _Up> + constexpr void operator()(_Tp&& __t, _Up&& __u) const + noexcept(noexcept(swap(_VSTD::forward<_Tp>(__t), _VSTD::forward<_Up>(__u)))) + { + swap(_VSTD::forward<_Tp>(__t), _VSTD::forward<_Up>(__u)); + } + + // 2.2 Otherwise, if `E1` and `E2` are lvalues of array types with equal extent and... + template + requires __swappable_arrays<_Tp, _Up, _Size> + constexpr void operator()(_Tp(& __t)[_Size], _Up(& __u)[_Size]) const + noexcept(noexcept((*this)(*__t, *__u))) + { + // FIXME(cjdb): replace loop with ranges::swap_ranges. + for (size_t __i = 0; __i < _Size; ++__i) { + (*this)(__t[__i], __u[__i]); + } + } + + // 2.3 Otherwise, if `E1` and `E2` are lvalues of the same type `T` that models... + template<__exchangeable _Tp> + constexpr void operator()(_Tp& __x, _Tp& __y) const + noexcept(is_nothrow_move_constructible_v<_Tp> && is_nothrow_move_assignable_v<_Tp>) + { + __y = _VSTD::exchange(__x, std::move(__y)); + } + }; +} + +namespace ranges { + inline constexpr auto swap = __swap::__fn{}; +} + +template +concept swappable = requires(_Tp& __a, _Tp& __b) { ranges::swap(__a, __b); }; + // [concept.copyconstructible] template concept copy_constructible = diff --git a/libcxx/test/std/concepts/lang/swappable.h b/libcxx/test/std/concepts/lang/swappable.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/concepts/lang/swappable.h @@ -0,0 +1,271 @@ +//===----------------------------------------------------------------------===// +// +// 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_STD_CONCEPTS_LANG_SWAPPABLE_H +#define TEST_STD_CONCEPTS_LANG_SWAPPABLE_H + +#include + +// `adl_swappable` indicates it's been swapped using ADL by maintaining a pointer to itself that +// isn't a part of the exchange. This is well-formed since we say that two `adl_swappable` objects +// are equal only if their respective `value` subobjects are equal and their respective `this` +// subobjects store the addresses of those respective `adl_swappable` objects. +class lvalue_adl_swappable { +public: + lvalue_adl_swappable() = default; + + constexpr lvalue_adl_swappable(int value) noexcept : value_(value) {} + + constexpr lvalue_adl_swappable(lvalue_adl_swappable&& other) noexcept + : value_(std::move(other.value_)), + this_(this) {} + + constexpr lvalue_adl_swappable(lvalue_adl_swappable const& other) noexcept + : value_(other.value_), + this_(this) {} + + constexpr lvalue_adl_swappable& + operator=(lvalue_adl_swappable other) noexcept { + value_ = other.value_; + return *this; + } + + constexpr friend void swap(lvalue_adl_swappable& x, + lvalue_adl_swappable& y) noexcept { + std::ranges::swap(x.value_, y.value_); + } + + constexpr bool operator==(lvalue_adl_swappable const& other) const noexcept { + return value_ == other.value_ && this_ == this && other.this_ == &other; + } + +private: + int value_{}; + lvalue_adl_swappable* this_ = this; +}; + +class lvalue_rvalue_adl_swappable_with { +public: + lvalue_rvalue_adl_swappable_with() = default; + + constexpr lvalue_rvalue_adl_swappable_with(int value) noexcept + : value_(value) {} + + constexpr lvalue_rvalue_adl_swappable_with( + lvalue_rvalue_adl_swappable_with&& other) noexcept + : value_(std::move(other.value_)), + this_(this) {} + + constexpr lvalue_rvalue_adl_swappable_with( + lvalue_rvalue_adl_swappable_with const& other) noexcept + : value_(other.value_), + this_(this) {} + + constexpr lvalue_rvalue_adl_swappable_with& + operator=(lvalue_rvalue_adl_swappable_with other) noexcept { + value_ = other.value_; + return *this; + } + + constexpr friend void swap(lvalue_rvalue_adl_swappable_with& x, + lvalue_rvalue_adl_swappable_with&& y) noexcept { + std::ranges::swap(x.value_, y.value_); + } + + constexpr bool + operator==(lvalue_rvalue_adl_swappable_with const& other) const noexcept { + return value_ == other.value_ && this_ == this && other.this_ == &other; + } + +private: + int value_{}; + lvalue_rvalue_adl_swappable_with* this_ = this; +}; + +class rvalue_lvalue_adl_swappable_with { +public: + rvalue_lvalue_adl_swappable_with() = default; + + constexpr rvalue_lvalue_adl_swappable_with(int value) noexcept + : value_(value) {} + + constexpr rvalue_lvalue_adl_swappable_with( + rvalue_lvalue_adl_swappable_with&& other) noexcept + : value_(std::move(other.value_)), + this_(this) {} + + constexpr rvalue_lvalue_adl_swappable_with( + rvalue_lvalue_adl_swappable_with const& other) noexcept + : value_(other.value_), + this_(this) {} + + constexpr rvalue_lvalue_adl_swappable_with& + operator=(rvalue_lvalue_adl_swappable_with other) noexcept { + value_ = other.value_; + return *this; + } + + constexpr friend void swap(rvalue_lvalue_adl_swappable_with&& x, + rvalue_lvalue_adl_swappable_with& y) noexcept { + std::ranges::swap(x.value_, y.value_); + } + + constexpr bool + operator==(rvalue_lvalue_adl_swappable_with const& other) const noexcept { + return value_ == other.value_ && this_ == this && other.this_ == &other; + } + +private: + int value_{}; + rvalue_lvalue_adl_swappable_with* this_ = this; +}; + +class rvalue_adl_swappable_with { +public: + rvalue_adl_swappable_with() = default; + + constexpr rvalue_adl_swappable_with(int value) noexcept : value_(value) {} + + constexpr + rvalue_adl_swappable_with(rvalue_adl_swappable_with&& other) noexcept + : value_(std::move(other.value_)), + this_(this) {} + + constexpr + rvalue_adl_swappable_with(rvalue_adl_swappable_with const& other) noexcept + : value_(other.value_), + this_(this) {} + + constexpr rvalue_adl_swappable_with& + operator=(rvalue_adl_swappable_with other) noexcept { + value_ = other.value_; + return *this; + } + + constexpr friend void swap(rvalue_adl_swappable_with&& x, + rvalue_adl_swappable_with&& y) noexcept { + std::ranges::swap(x.value_, y.value_); + } + + constexpr bool + operator==(rvalue_adl_swappable_with const& other) const noexcept { + return value_ == other.value_ && this_ == this && other.this_ == &other; + } + +private: + int value_{}; + rvalue_adl_swappable_with* this_ = this; +}; + +class non_move_constructible_adl_swappable { +public: + non_move_constructible_adl_swappable() = default; + + constexpr non_move_constructible_adl_swappable(int value) noexcept + : value_(value) {} + + constexpr non_move_constructible_adl_swappable( + non_move_constructible_adl_swappable&& other) noexcept + : value_(std::move(other.value_)), + this_(this) {} + + constexpr non_move_constructible_adl_swappable( + non_move_constructible_adl_swappable const& other) noexcept + : value_(other.value_), + this_(this) {} + + constexpr non_move_constructible_adl_swappable& + operator=(non_move_constructible_adl_swappable other) noexcept { + value_ = other.value_; + return *this; + } + + constexpr friend void swap(non_move_constructible_adl_swappable& x, + non_move_constructible_adl_swappable& y) noexcept { + std::ranges::swap(x.value_, y.value_); + } + + constexpr bool + operator==(non_move_constructible_adl_swappable const& other) const noexcept { + return value_ == other.value_ && this_ == this && other.this_ == &other; + } + +private: + int value_{}; + non_move_constructible_adl_swappable* this_ = this; +}; + +class non_move_assignable_adl_swappable { +public: + non_move_assignable_adl_swappable() = default; + + constexpr non_move_assignable_adl_swappable(int value) noexcept + : value_(value) {} + + non_move_assignable_adl_swappable(non_move_assignable_adl_swappable&& other) = + delete; + + constexpr non_move_assignable_adl_swappable( + non_move_assignable_adl_swappable const& other) noexcept + : value_(other.value_), + this_(this) {} + + constexpr non_move_assignable_adl_swappable& + operator=(non_move_assignable_adl_swappable&& other) noexcept = delete; + + constexpr friend void swap(non_move_assignable_adl_swappable& x, + non_move_assignable_adl_swappable& y) noexcept { + std::ranges::swap(x.value_, y.value_); + } + + constexpr bool + operator==(non_move_assignable_adl_swappable const& other) const noexcept { + return value_ == other.value_ && this_ == this && other.this_ == &other; + } + +private: + int value_{}; + non_move_assignable_adl_swappable* this_ = this; +}; + +class throwable_adl_swappable { +public: + throwable_adl_swappable() = default; + + constexpr throwable_adl_swappable(int value) noexcept : value_(value) {} + + constexpr throwable_adl_swappable(throwable_adl_swappable&& other) noexcept + : value_(std::move(other.value_)), + this_(this) {} + + constexpr + throwable_adl_swappable(throwable_adl_swappable const& other) noexcept + : value_(other.value_), + this_(this) {} + + constexpr throwable_adl_swappable& + operator=(throwable_adl_swappable other) noexcept { + value_ = other.value_; + return *this; + } + + constexpr friend void swap(throwable_adl_swappable& X, + throwable_adl_swappable& Y) noexcept(false) { + std::ranges::swap(X.value_, Y.value_); + } + + constexpr bool + operator==(throwable_adl_swappable const& other) const noexcept { + return value_ == other.value_ && this_ == this && other.this_ == &other; + } + +private: + int value_{}; + throwable_adl_swappable* this_ = this; +}; + +#endif // TEST_STD_CONCEPTS_LANG_SWAPPABLE_H diff --git a/libcxx/test/std/concepts/lang/swappable.pass.cpp b/libcxx/test/std/concepts/lang/swappable.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/concepts/lang/swappable.pass.cpp @@ -0,0 +1,251 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// template +// concept swappable = // see below + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" +#include "swappable.h" + +template +struct expected { + T x; + T y; +}; + +// Checks [concept.swappable]/2.1 +template +requires std::same_as, std::remove_cvref_t >&& + std::swappable > + constexpr bool check_swap_21(T&& x, U&& y) { + expected > const e{y, x}; + std::ranges::swap(std::forward(x), std::forward(y)); + return x == e.x && y == e.y; +} + +// Checks [concept.swappable]/2.2 +template +constexpr bool check_swap_22(T (&x)[N], T (&y)[N]) { + expected e; + std::copy(y, y + N, e.x); + std::copy(x, x + N, e.y); + + std::ranges::swap(x, y); + return std::equal(x, x + N, e.x, e.x + N) && + std::equal(y, y + N, e.y, e.y + N); +} + +// Checks [concept.swappable]/2.3 +template +requires std::copy_constructible > constexpr bool +check_swap_23(T x, T y) { + expected > const e{y, x}; + std::ranges::swap(x, y); + return x == e.x && y == e.y; +} + +static_assert([] { + auto x = lvalue_adl_swappable(0); + auto y = lvalue_adl_swappable(1); + return check_swap_21(x, y) && noexcept(std::ranges::swap(x, y)); +}()); +static_assert([] { + return check_swap_21(rvalue_adl_swappable_with(0), + rvalue_adl_swappable_with(1)) && + noexcept(std::ranges::swap(rvalue_adl_swappable_with(0), + rvalue_adl_swappable_with(1))); +}()); +static_assert([] { + auto x = lvalue_rvalue_adl_swappable_with(0); + return check_swap_21(x, lvalue_rvalue_adl_swappable_with(1)) && + noexcept(std::ranges::swap(x, lvalue_rvalue_adl_swappable_with(1))); +}()); +static_assert([] { + auto x = rvalue_lvalue_adl_swappable_with(0); + return check_swap_21(rvalue_lvalue_adl_swappable_with(1), x) && + noexcept(std::ranges::swap(rvalue_lvalue_adl_swappable_with(1), x)); +}()); +static_assert([] { + auto x = throwable_adl_swappable{0}; + auto y = throwable_adl_swappable{1}; + return check_swap_21(x, y) && !noexcept(std::ranges::swap(x, y)); +}()); +static_assert([] { + auto x = non_move_constructible_adl_swappable{0}; + auto y = non_move_constructible_adl_swappable{1}; + return check_swap_21(x, y) && noexcept(std::ranges::swap(x, y)); +}()); +static_assert([] { + auto x = non_move_assignable_adl_swappable{0}; + auto y = non_move_assignable_adl_swappable{1}; + return check_swap_21(x, y) && noexcept(std::ranges::swap(x, y)); +}()); + +namespace swappable_namespace { +enum unscoped { hello, world }; +void swap(unscoped&, unscoped&); + +enum class scoped { hello, world }; +void swap(scoped&, scoped&); +} // namespace swappable_namespace + +static_assert(std::swappable); +static_assert(std::swappable); + +static_assert([] { + int x[] = {0, 1, 2, 3, 4}; + int y[] = {5, 6, 7, 8, 9}; + return check_swap_22(x, y) && noexcept(std::ranges::swap(x, y)); +}()); +static_assert([] { + int x[] = {5, 6, 7, 8, 9}; + int y[] = {0, 1, 2, 3, 4}; + return check_swap_22(x, y) && noexcept(std::ranges::swap(x, y)); +}()); +static_assert([] { + lvalue_adl_swappable x[] = {{0}, {1}, {2}, {3}}; + lvalue_adl_swappable y[] = {{4}, {5}, {6}, {7}}; + return check_swap_22(x, y) && noexcept(std::ranges::swap(x, y)); +}()); +static_assert([] { + throwable_adl_swappable x[] = {{0}, {1}, {2}, {3}}; + throwable_adl_swappable y[] = {{4}, {5}, {6}, {7}}; + return check_swap_22(x, y) && !noexcept(std::ranges::swap(x, y)); +}()); + +inline auto global_x = 0; +static_assert(check_swap_23(0, 0) && + noexcept(std::ranges::swap(global_x, global_x))); +static_assert(check_swap_23(0, 1) && + noexcept(std::ranges::swap(global_x, global_x))); +static_assert(check_swap_23(1, 0) && + noexcept(std::ranges::swap(global_x, global_x))); + +static_assert([] { + int x = 42; + int y = 64; + return check_swap_23(x, y) && noexcept(std::ranges::swap(x, y)); +}()); +static_assert([] { + char const* x = "hello"; + return check_swap_23(x, nullptr) && + noexcept(std::ranges::swap(x, x)); +}()); + +// All tests for std::swappable are implicitly confirmed by `check_swap`, so we only need to +// sanity check for a few positive cases. +static_assert(std::swappable); +static_assert(std::swappable); +static_assert(std::swappable); +static_assert(std::swappable); +static_assert(std::swappable >); + +static_assert(!std::swappable); +static_assert(!std::swappable); + +struct non_move_constructible { + non_move_constructible(non_move_constructible&&) = delete; + non_move_constructible& operator=(non_move_constructible&&); +}; +static_assert(!std::move_constructible); +static_assert( + std::assignable_from); +static_assert(!std::swappable); + +struct non_move_assignable { + non_move_assignable(non_move_assignable&&); + non_move_assignable& operator=(non_move_assignable&&) = delete; +}; +static_assert(std::move_constructible); +static_assert(!std::assignable_from); +static_assert(!std::swappable); + +template +void check_swap(expected const& e) { + auto a = e.y; + auto b = e.x; + + std::ranges::swap(a, b); + assert(a == e.x); + assert(b == e.y); + + std::ranges::swap(a, b); + assert(a == e.y); + assert(b == e.x); + + static_assert(noexcept(std::ranges::swap(a, b)) == is_noexcept); +} + +int main(int, char**) { + { + auto const e = expected >{ + .x = {6, 7, 8, 9}, + .y = {0, 1, 2, 3, 4, 5}, + }; + check_swap(e); + } + { + auto const e = expected >{ + .x = {{0, "whole"}, {1, "cashews"}}, + .y = {{-1, "roasted"}, {2, "&"}, {-3, "salted"}}, + }; + check_swap(e); + } + { + auto const e = expected{ + .x = "hello there", + .y = "general kenobi", + }; + check_swap(e); + } + { + auto const e = expected >{ + .x = {10}, + .y = {20}, + }; + check_swap(e); + } + { + auto const e = expected >{ + .x = {10}, + .y = {20}, + }; + check_swap(e); + } + { + auto const e = expected >{ + .x = {{0, "whole"}, {1, "cashews"}}, + .y = {{-1, "roasted"}, {2, "&"}, {-3, "salted"}}, + }; + check_swap(e); + } + { + auto const e = expected >{ + .x = {0, 1, 2, 3, 4, 5}, + .y = {6, 7, 8, 9}, + }; + + check_swap(e); + } + return 0; +}