From a0839b14df6de99fe29bee7cdfff182d50de665d Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Tue, 31 Jul 2018 11:56:20 -0400 Subject: [PATCH] [libc++] Fix tuple assignment from types derived from a tuple-like The implementation of tuple's constructors and assignment operators currently diverges from the way the Standard specifies them, which leads to subtle cases where the behavior is not as specified. In particular, a class derived from a tuple-like type (e.g. pair) can't be assigned to a tuple with corresponding members, when it should. This commit re-implements the assignment operators (BUT NOT THE CONSTRUCTORS) in a way much closer to the specification to get rid of this bug. Most of the tests have been stolen from Eric's patch https://reviews.llvm.org/D27606. As a fly-by improvement, tests for noexcept correctness have been added to all overloads of operator=. We should tackle the same issue for the tuple constructors in a future patch - I'm just trying to make progress on fixing this long-standing bug. PR17550 rdar://15837420 Differential Revision: https://reviews.llvm.org/D50106 --- libcxx/include/tuple | 203 +++++++++++++-------- .../tuple.assign/array.extension.pass.cpp | 104 +++++++++++ .../tuple_array_template_depth.pass.cpp | 0 .../tuple.tuple/tuple.assign/const_pair.pass.cpp | 33 +++- .../tuple.tuple/tuple.assign/convert_copy.pass.cpp | 22 ++- .../tuple.tuple/tuple.assign/convert_move.pass.cpp | 22 ++- .../tuple/tuple.tuple/tuple.assign/copy.pass.cpp | 16 +- .../tuple.assign/derived_from_tuple_like.pass.cpp | 120 ++++++++++++ .../tuple.tuple/tuple.assign/laziness.pass.cpp | 77 ++++++++ .../tuple/tuple.tuple/tuple.assign/move.pass.cpp | 27 ++- .../tuple.tuple/tuple.assign/move_pair.pass.cpp | 38 +++- libcxx/test/support/propagate_value_category.hpp | 153 ++++++++++++++++ 12 files changed, 725 insertions(+), 90 deletions(-) create mode 100644 libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/array.extension.pass.cpp rename libcxx/test/{std => libcxx}/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp (100%) create mode 100644 libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp create mode 100644 libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.pass.cpp create mode 100644 libcxx/test/support/propagate_value_category.hpp diff --git a/libcxx/include/tuple b/libcxx/include/tuple index c3c7db5..58ae4cf 100644 --- a/libcxx/include/tuple +++ b/libcxx/include/tuple @@ -55,8 +55,7 @@ public: explicit(see-below) tuple(allocator_arg_t, const Alloc& a, pair&&); tuple& operator=(const tuple&); - tuple& - operator=(tuple&&) noexcept(AND(is_nothrow_move_assignable::value ...)); + tuple& operator=(tuple&&) noexcept(is_nothrow_move_assignable_v && ...); template tuple& operator=(const tuple&); template @@ -66,6 +65,11 @@ public: template tuple& operator=(pair&&); // iff sizeof...(T) == 2 + template + tuple& operator=(array const&) // iff sizeof...(T) == N, EXTENSION + template + tuple& operator=(array&&) // iff sizeof...(T) == N, EXTENSION + void swap(tuple&) noexcept(AND(swap(declval(), declval())...)); }; @@ -257,15 +261,6 @@ public: __tuple_leaf(const __tuple_leaf& __t) = default; __tuple_leaf(__tuple_leaf&& __t) = default; - template - _LIBCPP_INLINE_VISIBILITY - __tuple_leaf& - operator=(_Tp&& __t) _NOEXCEPT_((is_nothrow_assignable<_Hp&, _Tp>::value)) - { - __value_ = _VSTD::forward<_Tp>(__t); - return *this; - } - _LIBCPP_INLINE_VISIBILITY int swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value) { @@ -331,15 +326,6 @@ public: __tuple_leaf(__tuple_leaf const &) = default; __tuple_leaf(__tuple_leaf &&) = default; - template - _LIBCPP_INLINE_VISIBILITY - __tuple_leaf& - operator=(_Tp&& __t) _NOEXCEPT_((is_nothrow_assignable<_Hp&, _Tp>::value)) - { - _Hp::operator=(_VSTD::forward<_Tp>(__t)); - return *this; - } - _LIBCPP_INLINE_VISIBILITY int swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value) @@ -429,49 +415,30 @@ struct _LIBCPP_DECLSPEC_EMPTY_BASES __tuple_impl<__tuple_indices<_Indx...>, _Tp. typename __make_tuple_types<_Tuple>::type>::type>(_VSTD::get<_Indx>(__t)))... {} - template - _LIBCPP_INLINE_VISIBILITY - typename enable_if - < - __tuple_assignable<_Tuple, tuple<_Tp...> >::value, - __tuple_impl& - >::type - operator=(_Tuple&& __t) _NOEXCEPT_((__all::type>::type>::value...>::value)) - { - __swallow(__tuple_leaf<_Indx, _Tp>::operator=(_VSTD::forward::type>::type>(_VSTD::get<_Indx>(__t)))...); - return *this; - } - __tuple_impl(const __tuple_impl&) = default; __tuple_impl(__tuple_impl&&) = default; _LIBCPP_INLINE_VISIBILITY - __tuple_impl& - operator=(const __tuple_impl& __t) _NOEXCEPT_((__all::value...>::value)) - { - __swallow(__tuple_leaf<_Indx, _Tp>::operator=(static_cast&>(__t).get())...); - return *this; - } - - _LIBCPP_INLINE_VISIBILITY - __tuple_impl& - operator=(__tuple_impl&& __t) _NOEXCEPT_((__all::value...>::value)) - { - __swallow(__tuple_leaf<_Indx, _Tp>::operator=(_VSTD::forward<_Tp>(static_cast<__tuple_leaf<_Indx, _Tp>&>(__t).get()))...); - return *this; - } - - _LIBCPP_INLINE_VISIBILITY void swap(__tuple_impl& __t) _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value) { - __swallow(__tuple_leaf<_Indx, _Tp>::swap(static_cast<__tuple_leaf<_Indx, _Tp>&>(__t))...); + _VSTD::__swallow(__tuple_leaf<_Indx, _Tp>::swap(static_cast<__tuple_leaf<_Indx, _Tp>&>(__t))...); } }; +template +_LIBCPP_INLINE_VISIBILITY +void __memberwise_copy_assign(_Dest& __dest, _Source const& __source, __tuple_indices<_Np...>) { + _VSTD::__swallow(((_VSTD::get<_Np>(__dest) = _VSTD::get<_Np>(__source)), void(), 0)...); +} +template +_LIBCPP_INLINE_VISIBILITY +void __memberwise_forward_assign(_Dest& __dest, _Source&& __source, __tuple_types<_Up...>, __tuple_indices<_Np...>) { + _VSTD::__swallow((( + _VSTD::get<_Np>(__dest) = _VSTD::forward<_Up>(_VSTD::get<_Np>(_VSTD::forward<_Source>(__source))) + ), void(), 0)...); +} template class _LIBCPP_TEMPLATE_VIS tuple @@ -916,39 +883,129 @@ public: tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t) : __base_(allocator_arg_t(), __a, _VSTD::forward<_Tuple>(__t)) {} - using _CanCopyAssign = __all::value...>; - using _CanMoveAssign = __all::value...>; + // [tuple.assign] + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(_If<_And...>::value, tuple, __nat> const& __tuple) + _NOEXCEPT_((_And...>::value)) + { + _VSTD::__memberwise_copy_assign(*this, __tuple, + typename __make_tuple_indices::type()); + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(_If<_And...>::value, tuple, __nat>&& __tuple) + _NOEXCEPT_((_And...>::value)) + { + _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple), + __tuple_types<_Tp...>(), + typename __make_tuple_indices::type()); + return *this; + } + template, + is_assignable<_Tp&, _Up const&>... + >::value + ,int> = 0> _LIBCPP_INLINE_VISIBILITY - tuple& operator=(typename conditional<_CanCopyAssign::value, tuple, __nat>::type const& __t) - _NOEXCEPT_((__all::value...>::value)) + tuple& operator=(tuple<_Up...> const& __tuple) + _NOEXCEPT_((_And...>::value)) { - __base_.operator=(__t.__base_); + _VSTD::__memberwise_copy_assign(*this, __tuple, + typename __make_tuple_indices::type()); return *this; } + template, + is_assignable<_Tp&, _Up>... + >::value + ,int> = 0> _LIBCPP_INLINE_VISIBILITY - tuple& operator=(typename conditional<_CanMoveAssign::value, tuple, __nat>::type&& __t) - _NOEXCEPT_((__all::value...>::value)) + tuple& operator=(tuple<_Up...>&& __tuple) + _NOEXCEPT_((_And...>::value)) { - __base_.operator=(static_cast<_BaseT&&>(__t.__base_)); + _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple), + __tuple_types<_Up...>(), + typename __make_tuple_indices::type()); return *this; } - template ::value - >::type - > - _LIBCPP_INLINE_VISIBILITY - tuple& - operator=(_Tuple&& __t) _NOEXCEPT_((is_nothrow_assignable<_BaseT&, _Tuple>::value)) - { - __base_.operator=(_VSTD::forward<_Tuple>(__t)); - return *this; - } + template, + is_assignable<_FirstType<_Tp..., _Dep>&, _Up1 const&>, + is_assignable<_SecondType<_Tp..., _Dep>&, _Up2 const&> + >::value + ,int> = 0> + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(pair<_Up1, _Up2> const& __pair) + _NOEXCEPT_((_And< + is_nothrow_assignable<_FirstType<_Tp...>&, _Up1 const&>, + is_nothrow_assignable<_SecondType<_Tp...>&, _Up2 const&> + >::value)) + { + _VSTD::get<0>(*this) = __pair.first; + _VSTD::get<1>(*this) = __pair.second; + return *this; + } + + template, + is_assignable<_FirstType<_Tp..., _Dep>&, _Up1>, + is_assignable<_SecondType<_Tp..., _Dep>&, _Up2> + >::value + ,int> = 0> + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(pair<_Up1, _Up2>&& __pair) + _NOEXCEPT_((_And< + is_nothrow_assignable<_FirstType<_Tp...>&, _Up1>, + is_nothrow_assignable<_SecondType<_Tp...>&, _Up2> + >::value)) + { + _VSTD::get<0>(*this) = _VSTD::move(__pair.first); + _VSTD::get<1>(*this) = _VSTD::move(__pair.second); + return *this; + } + + // EXTENSION + template, + is_assignable<_Tp&, _Up const&>... + >::value + > > + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(array<_Up, _Np> const& __array) + _NOEXCEPT_((_And...>::value)) + { + _VSTD::__memberwise_copy_assign(*this, __array, + typename __make_tuple_indices::type()); + return *this; + } + + // EXTENSION + template, + is_assignable<_Tp&, _Up>... + >::value + > > + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(array<_Up, _Np>&& __array) + _NOEXCEPT_((_And...>::value)) + { + _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__array), + __tuple_types<_If...>(), + typename __make_tuple_indices::type()); + return *this; + } + // [tuple.swap] _LIBCPP_INLINE_VISIBILITY void swap(tuple& __t) _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value) {__base_.swap(__t.__base_);} diff --git a/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/array.extension.pass.cpp b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/array.extension.pass.cpp new file mode 100644 index 0000000..c7847e3 --- /dev/null +++ b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/array.extension.pass.cpp @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// + +// template class tuple; + +// EXTENSION +// template +// tuple& operator=(const array& u); +// +// template +// tuple& operator=(array&& u); + +// UNSUPPORTED: c++03 + +#include +#include +#include +#include +#include + + +template +struct NothrowAssignableFrom { + NothrowAssignableFrom& operator=(T) noexcept { return *this; } +}; + +template +struct PotentiallyThrowingAssignableFrom { + PotentiallyThrowingAssignableFrom& operator=(T) { return *this; } +}; + +int main(int, char**) { + // Tests for the array const& overload + { + std::array array = {1l, 2l, 3l}; + std::tuple tuple; + tuple = array; + assert(std::get<0>(tuple) == 1); + assert(std::get<1>(tuple) == 2); + assert(std::get<2>(tuple) == 3); + } + { + typedef std::tuple> Tuple; + typedef std::array Array; + static_assert(std::is_nothrow_assignable::value, ""); + } + { + typedef std::tuple> Tuple; + typedef std::array Array; + static_assert(std::is_assignable::value, ""); + static_assert(!std::is_nothrow_assignable::value, ""); + } + + // Tests for the array&& overload + { + std::array array = {1l, 2l, 3l}; + std::tuple tuple; + tuple = std::move(array); + assert(std::get<0>(tuple) == 1); + assert(std::get<1>(tuple) == 2); + assert(std::get<2>(tuple) == 3); + } + { + typedef std::tuple> Tuple; + typedef std::array Array; + static_assert(std::is_nothrow_assignable::value, ""); + } + { + typedef std::tuple> Tuple; + typedef std::array Array; + static_assert(std::is_assignable::value, ""); + static_assert(!std::is_nothrow_assignable::value, ""); + } + + // Test lvalue-refs and const rvalue-ref + { + typedef std::tuple> Tuple; + typedef std::array Array; + static_assert(std::is_nothrow_assignable::value, ""); + static_assert(std::is_nothrow_assignable::value, ""); + } + + { + typedef std::tuple> Tuple; + static_assert(!std::is_assignable&>::value, ""); + static_assert(!std::is_assignable&&>::value, ""); + static_assert(!std::is_assignable&>::value, ""); + static_assert(!std::is_assignable&&>::value, ""); + + static_assert(!std::is_assignable&>::value, ""); + static_assert(!std::is_assignable&&>::value, ""); + static_assert(!std::is_assignable&>::value, ""); + static_assert(!std::is_assignable&&>::value, ""); + } + + return 0; +} diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp similarity index 100% rename from libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp rename to libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp index 8edb818..e865244 100644 --- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp +++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp @@ -15,10 +15,18 @@ // UNSUPPORTED: c++03 +#include +#include #include +#include #include -#include -#include + +struct NothrowCopyAssignable { + NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; } +}; +struct PotentiallyThrowingCopyAssignable { + PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; } +}; #include "test_macros.h" @@ -40,6 +48,25 @@ int main(int, char**) using P = std::tuple, std::unique_ptr>; static_assert(!std::is_assignable::value, ""); } + { + typedef std::tuple Tuple; + typedef std::pair Pair; + static_assert(std::is_nothrow_assignable::value, ""); + static_assert(std::is_nothrow_assignable::value, ""); + static_assert(std::is_nothrow_assignable::value, ""); + } + { + typedef std::tuple Tuple; + typedef std::pair Pair; + static_assert(std::is_assignable::value, ""); + static_assert(!std::is_nothrow_assignable::value, ""); + + static_assert(std::is_assignable::value, ""); + static_assert(!std::is_nothrow_assignable::value, ""); + + static_assert(std::is_assignable::value, ""); + static_assert(!std::is_nothrow_assignable::value, ""); + } - return 0; + return 0; } diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp index e02fc84..1c78e59 100644 --- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp +++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp @@ -39,6 +39,16 @@ struct NonAssignable { NonAssignable& operator=(NonAssignable&&) = delete; }; +struct NothrowCopyAssignable +{ + NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; } +}; + +struct PotentiallyThrowingCopyAssignable +{ + PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; } +}; + int main(int, char**) { { @@ -98,6 +108,16 @@ int main(int, char**) static_assert(!std::is_assignable::value, ""); static_assert(!std::is_assignable::value, ""); } + { + typedef std::tuple T0; + typedef std::tuple T1; + static_assert(std::is_nothrow_assignable::value, ""); + } + { + typedef std::tuple T0; + typedef std::tuple T1; + static_assert(!std::is_nothrow_assignable::value, ""); + } - return 0; + return 0; } diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp index 39bcc5d..9465d99 100644 --- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp +++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp @@ -50,6 +50,16 @@ struct NonAssignable { NonAssignable& operator=(NonAssignable&&) = delete; }; +struct NothrowMoveAssignable +{ + NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; } +}; + +struct PotentiallyThrowingMoveAssignable +{ + PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; } +}; + int main(int, char**) { { @@ -119,6 +129,16 @@ int main(int, char**) static_assert(!std::is_assignable::value, ""); static_assert(!std::is_assignable::value, ""); } + { + typedef std::tuple T0; + typedef std::tuple T1; + static_assert(std::is_nothrow_assignable::value, ""); + } + { + typedef std::tuple T0; + typedef std::tuple T1; + static_assert(!std::is_nothrow_assignable::value, ""); + } - return 0; + return 0; } diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp index bbccd67..71882a4 100644 --- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp +++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp @@ -34,6 +34,12 @@ struct MoveAssignable { MoveAssignable& operator=(MoveAssignable const&) = delete; MoveAssignable& operator=(MoveAssignable&&) = default; }; +struct NothrowCopyAssignable { + NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; } +}; +struct PotentiallyThrowingCopyAssignable { + PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; } +}; struct CopyAssignableInt { CopyAssignableInt& operator=(int&) { return *this; } @@ -119,6 +125,14 @@ int main(int, char**) using P = std::pair; static_assert(!std::is_assignable::value, ""); } + { + using T = std::tuple; + static_assert(std::is_nothrow_copy_assignable::value, ""); + } + { + using T = std::tuple; + static_assert(!std::is_nothrow_copy_assignable::value, ""); + } - return 0; + return 0; } diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp new file mode 100644 index 0000000..373b30a --- /dev/null +++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp @@ -0,0 +1,120 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// + +// template class tuple; + +// template +// tuple& operator=(const tuple& u); + +// UNSUPPORTED: c++03 + +#include +#include +#include +#include +#include + +#include "propagate_value_category.hpp" + +struct TracksIntQuals { + TracksIntQuals() : value(-1), value_category(VC_None), assigned(false) {} + + template ::type, TracksIntQuals>::value>::type> + TracksIntQuals(Tp &&x) + : value(x), value_category(getValueCategory()), assigned(false) { + static_assert(std::is_same, int>::value, ""); + } + + template ::type, TracksIntQuals>::value>::type> + TracksIntQuals &operator=(Tp &&x) { + static_assert(std::is_same, int>::value, ""); + value = x; + value_category = getValueCategory(); + assigned = true; + return *this; + } + + void reset() { + value = -1; + value_category = VC_None; + assigned = false; + } + + bool checkConstruct(int expect, ValueCategory expect_vc) const { + return value != 1 && value == expect && value_category == expect_vc && + assigned == false; + } + + bool checkAssign(int expect, ValueCategory expect_vc) const { + return value != 1 && value == expect && value_category == expect_vc && + assigned == true; + } + + int value; + ValueCategory value_category; + bool assigned; +}; + +template +struct DerivedFromTup : Tup { + using Tup::Tup; +}; + +template +void do_derived_assign_test() { + using Tup1 = std::tuple; + Tup1 t; + auto reset = [&]() { + std::get<0>(t) = -1; + std::get<1>(t).reset(); + }; + { + DerivedFromTup> d; + std::get<0>(d) = 42; + std::get<1>(d) = 101; + + t = ValueCategoryCast(d); + assert(std::get<0>(t) == 42); + assert(std::get<1>(t).checkAssign(101, VC)); + } + reset(); + { + DerivedFromTup> d; + std::get<0>(d) = 42; + std::get<1>(d) = 101; + + t = ValueCategoryCast(d); + assert(std::get<0>(t) == 42); + assert(std::get<1>(t).checkAssign(101, VC)); + } + reset(); + { +#ifdef _LIBCPP_VERSION // assignment from std::array is a libc++ extension + DerivedFromTup> d; + std::get<0>(d) = 42; + std::get<1>(d) = 101; + + t = ValueCategoryCast(d); + assert(std::get<0>(t) == 42); + assert(std::get<1>(t).checkAssign(101, VC)); +#endif + } +} + +int main(int, char**) { + do_derived_assign_test(); + do_derived_assign_test(); + + return 0; +} diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.pass.cpp new file mode 100644 index 0000000..267b876 --- /dev/null +++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.pass.cpp @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// This test ensures that std::tuple is lazy when it comes to checking whether +// the elements it is assigned from can be used to assign to the types in +// the tuple. + +#include +#include + +template +constexpr typename std::enable_if::type BlowUp() { + static_assert(Enable && sizeof...(Class) != sizeof...(Class), ""); + return true; +} + +template +struct Fail { + static_assert(sizeof(T) != sizeof(T), ""); + using type = void; +}; + +struct NoAssign { + NoAssign() = default; + NoAssign(NoAssign const&) = default; + template ::type> + NoAssign& operator=(T) { return *this; } +}; + +template +struct DieOnAssign { + DieOnAssign() = default; + template ::value>::type, + class = typename Fail::type> + DieOnAssign& operator=(T) { + return *this; + } +}; + +void test_arity_checks() { + { + using T = std::tuple, int>; + using P = std::pair; + static_assert(!std::is_assignable::value, ""); + } + { + using T = std::tuple >; + using A = std::array; + static_assert(!std::is_assignable::value, ""); + } +} + +void test_assignability_checks() { + { + using T1 = std::tuple >; + using T2 = std::tuple; + static_assert(!std::is_assignable::value, ""); + } + { + using T1 = std::tuple >; + using T2 = std::pair; + static_assert(!std::is_assignable::value, ""); + } +} + +int main(int, char**) { + test_arity_checks(); + test_assignability_checks(); + return 0; +} diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp index 292cfc2..cabed38 100644 --- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp +++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp @@ -35,7 +35,12 @@ struct MoveAssignable { MoveAssignable& operator=(MoveAssignable const&) = delete; MoveAssignable& operator=(MoveAssignable&&) = default; }; - +struct NothrowMoveAssignable { + NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; } +}; +struct PotentiallyThrowingMoveAssignable { + PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; } +}; struct CountAssign { static int copied; @@ -48,7 +53,6 @@ struct CountAssign { int CountAssign::copied = 0; int CountAssign::moved = 0; - int main(int, char**) { { @@ -102,7 +106,6 @@ int main(int, char**) using T = std::tuple>; static_assert(std::is_move_assignable::value, ""); static_assert(!std::is_copy_assignable::value, ""); - } { using T = std::tuple; @@ -123,6 +126,22 @@ int main(int, char**) assert(CountAssign::copied == 1); assert(CountAssign::moved == 0); } + { + using T = std::tuple; + static_assert(!std::is_move_assignable::value, ""); + } + { + using T = std::tuple; + static_assert(std::is_move_assignable::value, ""); + } + { + using T = std::tuple; + static_assert(std::is_nothrow_move_assignable::value, ""); + } + { + using T = std::tuple; + static_assert(!std::is_nothrow_move_assignable::value, ""); + } - return 0; + return 0; } diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp index 20315c0..3e083f5 100644 --- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp +++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp @@ -37,9 +37,20 @@ struct D explicit D(int i) : B(i) {} }; -struct NonMoveAssignable { - NonMoveAssignable& operator=(NonMoveAssignable const&) = default; - NonMoveAssignable& operator=(NonMoveAssignable&&) = delete; +struct NonAssignable +{ + NonAssignable& operator=(NonAssignable const&) = delete; + NonAssignable& operator=(NonAssignable&&) = delete; +}; + +struct NothrowMoveAssignable +{ + NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; } +}; + +struct PotentiallyThrowingMoveAssignable +{ + PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; } }; int main(int, char**) @@ -54,15 +65,28 @@ int main(int, char**) assert(std::get<1>(t1)->id_ == 3); } { - using T = std::tuple; - using P = std::pair; - static_assert(!std::is_assignable::value, ""); + using T = std::tuple; + using P = std::pair; + static_assert(!std::is_assignable::value, ""); } { using T = std::tuple; using P = std::pair; static_assert(!std::is_assignable::value, ""); } + { + typedef std::tuple Tuple; + typedef std::pair Pair; + static_assert(std::is_nothrow_assignable::value, ""); + static_assert(!std::is_assignable::value, ""); + } + { + typedef std::tuple Tuple; + typedef std::pair Pair; + static_assert(std::is_assignable::value, ""); + static_assert(!std::is_nothrow_assignable::value, ""); + static_assert(!std::is_assignable::value, ""); + } - return 0; + return 0; } diff --git a/libcxx/test/support/propagate_value_category.hpp b/libcxx/test/support/propagate_value_category.hpp new file mode 100644 index 0000000..02a035d --- /dev/null +++ b/libcxx/test/support/propagate_value_category.hpp @@ -0,0 +1,153 @@ +//===----------------------------------------------------------------------===// +// +// 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_SUPPORT_PROPAGATE_VALUE_CATEGORY +#define TEST_SUPPORT_PROPAGATE_VALUE_CATEGORY + +#include "test_macros.h" +#include + +#if TEST_STD_VER < 11 +#error this header may only be used in C++11 +#endif + +using UnderlyingVCType = unsigned; +enum ValueCategory : UnderlyingVCType { + VC_None = 0, + VC_LVal = 1 << 0, + VC_RVal = 1 << 1, + VC_Const = 1 << 2, + VC_Volatile = 1 << 3, + VC_ConstVolatile = VC_Const | VC_Volatile +}; + +inline constexpr ValueCategory operator&(ValueCategory LHS, ValueCategory RHS) { + return ValueCategory(LHS & (UnderlyingVCType)RHS); +} + +inline constexpr ValueCategory operator|(ValueCategory LHS, ValueCategory RHS) { + return ValueCategory(LHS | (UnderlyingVCType)RHS); +} + +inline constexpr ValueCategory operator^(ValueCategory LHS, ValueCategory RHS) { + return ValueCategory(LHS ^ (UnderlyingVCType)RHS); +} + +inline constexpr bool isValidValueCategory(ValueCategory VC) { + return (VC & (VC_LVal | VC_RVal)) != (VC_LVal | VC_RVal); +} + +inline constexpr bool hasValueCategory(ValueCategory Arg, ValueCategory Key) { + return Arg == Key || ((Arg & Key) == Key); +} + +template +using UnCVRef = + typename std::remove_cv::type>::type; + +template +constexpr ValueCategory getReferenceQuals() { + return std::is_lvalue_reference::value + ? VC_LVal + : (std::is_rvalue_reference::value ? VC_RVal : VC_None); +} +static_assert(getReferenceQuals() == VC_None, ""); +static_assert(getReferenceQuals() == VC_LVal, ""); +static_assert(getReferenceQuals() == VC_RVal, ""); + +template +constexpr ValueCategory getCVQuals() { + using Vp = typename std::remove_reference::type; + return std::is_const::value && std::is_volatile::value + ? VC_ConstVolatile + : (std::is_const::value + ? VC_Const + : (std::is_volatile::value ? VC_Volatile : VC_None)); +} +static_assert(getCVQuals() == VC_None, ""); +static_assert(getCVQuals() == VC_Const, ""); +static_assert(getCVQuals() == VC_Volatile, ""); +static_assert(getCVQuals() == VC_ConstVolatile, ""); +static_assert(getCVQuals() == VC_None, ""); +static_assert(getCVQuals() == VC_Const, ""); + +template +inline constexpr ValueCategory getValueCategory() { + return getReferenceQuals() | getCVQuals(); +} +static_assert(getValueCategory() == VC_None, ""); +static_assert(getValueCategory() == (VC_LVal | VC_Const), ""); +static_assert(getValueCategory() == + (VC_RVal | VC_ConstVolatile), + ""); + +template +struct ApplyValueCategory { +private: + static_assert(isValidValueCategory(VC), ""); + + template + using CondT = typename std::conditional::type; + +public: + template > + using ApplyCVQuals = CondT< + hasValueCategory(VC, VC_ConstVolatile), typename std::add_cv::type, + CondT::type, + CondT::type, Tp>>>; + + template ::type> + using ApplyReferenceQuals = + CondT::type, + CondT::type, Vp>>; + + template + using Apply = ApplyReferenceQuals>>; + + template ::type = true> + static Apply> cast(Tp &&t) { + using ToType = Apply>; + return static_cast(t); + } + + template ::type = true> + static Apply> cast(Tp &&t) { + using ToType = Apply>; + return static_cast(std::move(t)); + } + + template < + class Tp, bool Dummy = true, + typename std::enable_if::type = true> + static Apply> cast(Tp &&t) { + return t; + } +}; + +template +using ApplyValueCategoryT = typename ApplyValueCategory::template Apply; + +template +using PropagateValueCategory = ApplyValueCategory()>; + +template +using PropagateValueCategoryT = + typename ApplyValueCategory()>::template Apply; + +template +typename ApplyValueCategory::template Apply ValueCategoryCast(Tp &&t) { + return ApplyValueCategory::cast(std::forward(t)); +}; + +#endif // TEST_SUPPORT_PROPAGATE_VALUE_CATEGORY -- 2.7.4