diff options
author | Louis Dionne <ldionne@apple.com> | 2018-07-31 11:56:20 -0400 |
---|---|---|
committer | Louis Dionne <ldionne.2@gmail.com> | 2021-02-22 14:52:18 -0500 |
commit | a0839b14df6de99fe29bee7cdfff182d50de665d (patch) | |
tree | 62981fc220b5fcd6a0c461a00c249037c549a819 | |
parent | 946a09945f02427c7b2075ae72ea4d77bb1a2a1d (diff) | |
download | llvm-a0839b14df6de99fe29bee7cdfff182d50de665d.zip llvm-a0839b14df6de99fe29bee7cdfff182d50de665d.tar.gz llvm-a0839b14df6de99fe29bee7cdfff182d50de665d.tar.bz2 |
[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
12 files changed, 725 insertions, 90 deletions
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<U1, U2>&&); tuple& operator=(const tuple&); - tuple& - operator=(tuple&&) noexcept(AND(is_nothrow_move_assignable<T>::value ...)); + tuple& operator=(tuple&&) noexcept(is_nothrow_move_assignable_v<T> && ...); template <class... U> tuple& operator=(const tuple<U...>&); template <class... U> @@ -66,6 +65,11 @@ public: template <class U1, class U2> tuple& operator=(pair<U1, U2>&&); // iff sizeof...(T) == 2 + template<class U, size_t N> + tuple& operator=(array<U, N> const&) // iff sizeof...(T) == N, EXTENSION + template<class U, size_t N> + tuple& operator=(array<U, N>&&) // iff sizeof...(T) == N, EXTENSION + void swap(tuple&) noexcept(AND(swap(declval<T&>(), declval<T&>())...)); }; @@ -257,15 +261,6 @@ public: __tuple_leaf(const __tuple_leaf& __t) = default; __tuple_leaf(__tuple_leaf&& __t) = default; - template <class _Tp> - _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 <class _Tp> - _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 <class _Tuple> - _LIBCPP_INLINE_VISIBILITY - typename enable_if - < - __tuple_assignable<_Tuple, tuple<_Tp...> >::value, - __tuple_impl& - >::type - operator=(_Tuple&& __t) _NOEXCEPT_((__all<is_nothrow_assignable<_Tp&, typename tuple_element<_Indx, - typename __make_tuple_types<_Tuple>::type>::type>::value...>::value)) - { - __swallow(__tuple_leaf<_Indx, _Tp>::operator=(_VSTD::forward<typename tuple_element<_Indx, - typename __make_tuple_types<_Tuple>::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<is_nothrow_copy_assignable<_Tp>::value...>::value)) - { - __swallow(__tuple_leaf<_Indx, _Tp>::operator=(static_cast<const __tuple_leaf<_Indx, _Tp>&>(__t).get())...); - return *this; - } - - _LIBCPP_INLINE_VISIBILITY - __tuple_impl& - operator=(__tuple_impl&& __t) _NOEXCEPT_((__all<is_nothrow_move_assignable<_Tp>::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<class _Dest, class _Source, size_t ..._Np> +_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<class _Dest, class _Source, class ..._Up, size_t ..._Np> +_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 ..._Tp> 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<is_copy_assignable<_Tp>::value...>; - using _CanMoveAssign = __all<is_move_assignable<_Tp>::value...>; + // [tuple.assign] + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(_If<_And<is_copy_assignable<_Tp>...>::value, tuple, __nat> const& __tuple) + _NOEXCEPT_((_And<is_nothrow_copy_assignable<_Tp>...>::value)) + { + _VSTD::__memberwise_copy_assign(*this, __tuple, + typename __make_tuple_indices<sizeof...(_Tp)>::type()); + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(_If<_And<is_move_assignable<_Tp>...>::value, tuple, __nat>&& __tuple) + _NOEXCEPT_((_And<is_nothrow_move_assignable<_Tp>...>::value)) + { + _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple), + __tuple_types<_Tp...>(), + typename __make_tuple_indices<sizeof...(_Tp)>::type()); + return *this; + } + template<class... _Up, _EnableIf< + _And< + _BoolConstant<sizeof...(_Tp) == sizeof...(_Up)>, + is_assignable<_Tp&, _Up const&>... + >::value + ,int> = 0> _LIBCPP_INLINE_VISIBILITY - tuple& operator=(typename conditional<_CanCopyAssign::value, tuple, __nat>::type const& __t) - _NOEXCEPT_((__all<is_nothrow_copy_assignable<_Tp>::value...>::value)) + tuple& operator=(tuple<_Up...> const& __tuple) + _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up const&>...>::value)) { - __base_.operator=(__t.__base_); + _VSTD::__memberwise_copy_assign(*this, __tuple, + typename __make_tuple_indices<sizeof...(_Tp)>::type()); return *this; } + template<class... _Up, _EnableIf< + _And< + _BoolConstant<sizeof...(_Tp) == sizeof...(_Up)>, + is_assignable<_Tp&, _Up>... + >::value + ,int> = 0> _LIBCPP_INLINE_VISIBILITY - tuple& operator=(typename conditional<_CanMoveAssign::value, tuple, __nat>::type&& __t) - _NOEXCEPT_((__all<is_nothrow_move_assignable<_Tp>::value...>::value)) + tuple& operator=(tuple<_Up...>&& __tuple) + _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up>...>::value)) { - __base_.operator=(static_cast<_BaseT&&>(__t.__base_)); + _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple), + __tuple_types<_Up...>(), + typename __make_tuple_indices<sizeof...(_Tp)>::type()); return *this; } - template <class _Tuple, - class = typename enable_if - < - __tuple_assignable<_Tuple, tuple>::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<class _Up1, class _Up2, class _Dep = true_type, _EnableIf< + _And<_Dep, + _BoolConstant<sizeof...(_Tp) == 2>, + 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<class _Up1, class _Up2, class _Dep = true_type, _EnableIf< + _And<_Dep, + _BoolConstant<sizeof...(_Tp) == 2>, + 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<class _Up, size_t _Np, class = _EnableIf< + _And< + _BoolConstant<_Np == sizeof...(_Tp)>, + is_assignable<_Tp&, _Up const&>... + >::value + > > + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(array<_Up, _Np> const& __array) + _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up const&>...>::value)) + { + _VSTD::__memberwise_copy_assign(*this, __array, + typename __make_tuple_indices<sizeof...(_Tp)>::type()); + return *this; + } + + // EXTENSION + template<class _Up, size_t _Np, class = void, class = _EnableIf< + _And< + _BoolConstant<_Np == sizeof...(_Tp)>, + is_assignable<_Tp&, _Up>... + >::value + > > + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(array<_Up, _Np>&& __array) + _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up>...>::value)) + { + _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__array), + __tuple_types<_If<true, _Up, _Tp>...>(), + typename __make_tuple_indices<sizeof...(_Tp)>::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 +// +//===----------------------------------------------------------------------===// + +// <tuple> + +// template <class... Types> class tuple; + +// EXTENSION +// template <class U, size_t N> +// tuple& operator=(const array<U, N>& u); +// +// template <class U, size_t N> +// tuple& operator=(array<U, N>&& u); + +// UNSUPPORTED: c++03 + +#include <array> +#include <cassert> +#include <tuple> +#include <type_traits> +#include <utility> + + +template <class T> +struct NothrowAssignableFrom { + NothrowAssignableFrom& operator=(T) noexcept { return *this; } +}; + +template <class T> +struct PotentiallyThrowingAssignableFrom { + PotentiallyThrowingAssignableFrom& operator=(T) { return *this; } +}; + +int main(int, char**) { + // Tests for the array const& overload + { + std::array<long, 3> array = {1l, 2l, 3l}; + std::tuple<int, int, int> tuple; + tuple = array; + assert(std::get<0>(tuple) == 1); + assert(std::get<1>(tuple) == 2); + assert(std::get<2>(tuple) == 3); + } + { + typedef std::tuple<NothrowAssignableFrom<int>> Tuple; + typedef std::array<int, 1> Array; + static_assert(std::is_nothrow_assignable<Tuple&, Array const&>::value, ""); + } + { + typedef std::tuple<PotentiallyThrowingAssignableFrom<int>> Tuple; + typedef std::array<int, 1> Array; + static_assert(std::is_assignable<Tuple&, Array const&>::value, ""); + static_assert(!std::is_nothrow_assignable<Tuple&, Array const&>::value, ""); + } + + // Tests for the array&& overload + { + std::array<long, 3> array = {1l, 2l, 3l}; + std::tuple<int, int, int> 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<NothrowAssignableFrom<int>> Tuple; + typedef std::array<int, 1> Array; + static_assert(std::is_nothrow_assignable<Tuple&, Array&&>::value, ""); + } + { + typedef std::tuple<PotentiallyThrowingAssignableFrom<int>> Tuple; + typedef std::array<int, 1> Array; + static_assert(std::is_assignable<Tuple&, Array&&>::value, ""); + static_assert(!std::is_nothrow_assignable<Tuple&, Array&&>::value, ""); + } + + // Test lvalue-refs and const rvalue-ref + { + typedef std::tuple<NothrowAssignableFrom<int>> Tuple; + typedef std::array<int, 1> Array; + static_assert(std::is_nothrow_assignable<Tuple&, Array&>::value, ""); + static_assert(std::is_nothrow_assignable<Tuple&, const Array&&>::value, ""); + } + + { + typedef std::tuple<NothrowAssignableFrom<int>> Tuple; + static_assert(!std::is_assignable<Tuple&, std::array<long, 2>&>::value, ""); + static_assert(!std::is_assignable<Tuple&, std::array<long, 2>&&>::value, ""); + static_assert(!std::is_assignable<Tuple&, const std::array<long, 2>&>::value, ""); + static_assert(!std::is_assignable<Tuple&, const std::array<long, 2>&&>::value, ""); + + static_assert(!std::is_assignable<Tuple&, std::array<long, 4>&>::value, ""); + static_assert(!std::is_assignable<Tuple&, std::array<long, 4>&&>::value, ""); + static_assert(!std::is_assignable<Tuple&, const std::array<long, 4>&>::value, ""); + static_assert(!std::is_assignable<Tuple&, const std::array<long, 4>&&>::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 index 3d4262f..3d4262f 100644 --- 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 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 <cassert> +#include <memory> #include <tuple> +#include <type_traits> #include <utility> -#include <memory> -#include <cassert> + +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<int>, std::unique_ptr<int>>; static_assert(!std::is_assignable<T, const P &>::value, ""); } + { + typedef std::tuple<NothrowCopyAssignable, long> Tuple; + typedef std::pair<NothrowCopyAssignable, int> Pair; + static_assert(std::is_nothrow_assignable<Tuple&, Pair const&>::value, ""); + static_assert(std::is_nothrow_assignable<Tuple&, Pair&>::value, ""); + static_assert(std::is_nothrow_assignable<Tuple&, Pair const&&>::value, ""); + } + { + typedef std::tuple<PotentiallyThrowingCopyAssignable, long> Tuple; + typedef std::pair<PotentiallyThrowingCopyAssignable, int> Pair; + static_assert(std::is_assignable<Tuple&, Pair const&>::value, ""); + static_assert(!std::is_nothrow_assignable<Tuple&, Pair const&>::value, ""); + + static_assert(std::is_assignable<Tuple&, Pair&>::value, ""); + static_assert(!std::is_nothrow_assignable<Tuple&, Pair&>::value, ""); + + static_assert(std::is_assignable<Tuple&, Pair const&&>::value, ""); + static_assert(!std::is_nothrow_assignable<Tuple&, Pair const&&>::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<T, U const&>::value, ""); static_assert(!std::is_assignable<U, T const&>::value, ""); } + { + typedef std::tuple<NothrowCopyAssignable, long> T0; + typedef std::tuple<NothrowCopyAssignable, int> T1; + static_assert(std::is_nothrow_assignable<T0&, T1 const&>::value, ""); + } + { + typedef std::tuple<PotentiallyThrowingCopyAssignable, long> T0; + typedef std::tuple<PotentiallyThrowingCopyAssignable, int> T1; + static_assert(!std::is_nothrow_assignable<T0&, T1 const&>::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<T, U&&>::value, ""); static_assert(!std::is_assignable<U, T&&>::value, ""); } + { + typedef std::tuple<NothrowMoveAssignable, long> T0; + typedef std::tuple<NothrowMoveAssignable, int> T1; + static_assert(std::is_nothrow_assignable<T0&, T1&&>::value, ""); + } + { + typedef std::tuple<PotentiallyThrowingMoveAssignable, long> T0; + typedef std::tuple<PotentiallyThrowingMoveAssignable, int> T1; + static_assert(!std::is_nothrow_assignable<T0&, T1&&>::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<int, MoveAssignable>; static_assert(!std::is_assignable<T&, P&>::value, ""); } + { + using T = std::tuple<NothrowCopyAssignable, int>; + static_assert(std::is_nothrow_copy_assignable<T>::value, ""); + } + { + using T = std::tuple<PotentiallyThrowingCopyAssignable, int>; + static_assert(!std::is_nothrow_copy_assignable<T>::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 +// +//===----------------------------------------------------------------------===// + +// <tuple> + +// template <class... Types> class tuple; + +// template <class... UTypes> +// tuple& operator=(const tuple<UTypes...>& u); + +// UNSUPPORTED: c++03 + +#include <tuple> +#include <array> +#include <string> +#include <utility> +#include <cassert> + +#include "propagate_value_category.hpp" + +struct TracksIntQuals { + TracksIntQuals() : value(-1), value_category(VC_None), assigned(false) {} + + template <class Tp, + class = typename std::enable_if<!std::is_same< + typename std::decay<Tp>::type, TracksIntQuals>::value>::type> + TracksIntQuals(Tp &&x) + : value(x), value_category(getValueCategory<Tp &&>()), assigned(false) { + static_assert(std::is_same<UnCVRef<Tp>, int>::value, ""); + } + + template <class Tp, + class = typename std::enable_if<!std::is_same< + typename std::decay<Tp>::type, TracksIntQuals>::value>::type> + TracksIntQuals &operator=(Tp &&x) { + static_assert(std::is_same<UnCVRef<Tp>, int>::value, ""); + value = x; + value_category = getValueCategory<Tp &&>(); + 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 <class Tup> +struct DerivedFromTup : Tup { + using Tup::Tup; +}; + +template <ValueCategory VC> +void do_derived_assign_test() { + using Tup1 = std::tuple<long, TracksIntQuals>; + Tup1 t; + auto reset = [&]() { + std::get<0>(t) = -1; + std::get<1>(t).reset(); + }; + { + DerivedFromTup<std::tuple<int, int>> d; + std::get<0>(d) = 42; + std::get<1>(d) = 101; + + t = ValueCategoryCast<VC>(d); + assert(std::get<0>(t) == 42); + assert(std::get<1>(t).checkAssign(101, VC)); + } + reset(); + { + DerivedFromTup<std::pair<int, int>> d; + std::get<0>(d) = 42; + std::get<1>(d) = 101; + + t = ValueCategoryCast<VC>(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<std::array<int, 2>> d; + std::get<0>(d) = 42; + std::get<1>(d) = 101; + + t = ValueCategoryCast<VC>(d); + assert(std::get<0>(t) == 42); + assert(std::get<1>(t).checkAssign(101, VC)); +#endif + } +} + +int main(int, char**) { + do_derived_assign_test<VC_LVal | VC_Const>(); + do_derived_assign_test<VC_RVal>(); + + 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 <tuple> +#include <array> + +template <bool Enable, class ...Class> +constexpr typename std::enable_if<Enable, bool>::type BlowUp() { + static_assert(Enable && sizeof...(Class) != sizeof...(Class), ""); + return true; +} + +template<class T> +struct Fail { + static_assert(sizeof(T) != sizeof(T), ""); + using type = void; +}; + +struct NoAssign { + NoAssign() = default; + NoAssign(NoAssign const&) = default; + template <class T, class = typename std::enable_if<sizeof(T) != sizeof(T)>::type> + NoAssign& operator=(T) { return *this; } +}; + +template <int> +struct DieOnAssign { + DieOnAssign() = default; + template <class T, class X = typename std::enable_if<!std::is_same<T, DieOnAssign>::value>::type, + class = typename Fail<X>::type> + DieOnAssign& operator=(T) { + return *this; + } +}; + +void test_arity_checks() { + { + using T = std::tuple<int, DieOnAssign<0>, int>; + using P = std::pair<int, int>; + static_assert(!std::is_assignable<T&, P const&>::value, ""); + } + { + using T = std::tuple<int, int, DieOnAssign<1> >; + using A = std::array<int, 1>; + static_assert(!std::is_assignable<T&, A const&>::value, ""); + } +} + +void test_assignability_checks() { + { + using T1 = std::tuple<int, NoAssign, DieOnAssign<2> >; + using T2 = std::tuple<long, long, long>; + static_assert(!std::is_assignable<T1&, T2 const&>::value, ""); + } + { + using T1 = std::tuple<NoAssign, DieOnAssign<3> >; + using T2 = std::pair<long, double>; + static_assert(!std::is_assignable<T1&, T2 const&>::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<std::unique_ptr<int>>; static_assert(std::is_move_assignable<T>::value, ""); static_assert(!std::is_copy_assignable<T>::value, ""); - } { using T = std::tuple<int, NonAssignable>; @@ -123,6 +126,22 @@ int main(int, char**) assert(CountAssign::copied == 1); assert(CountAssign::moved == 0); } + { + using T = std::tuple<int, NonAssignable>; + static_assert(!std::is_move_assignable<T>::value, ""); + } + { + using T = std::tuple<int, MoveAssignable>; + static_assert(std::is_move_assignable<T>::value, ""); + } + { + using T = std::tuple<NothrowMoveAssignable, int>; + static_assert(std::is_nothrow_move_assignable<T>::value, ""); + } + { + using T = std::tuple<PotentiallyThrowingMoveAssignable, int>; + static_assert(!std::is_nothrow_move_assignable<T>::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<int, NonMoveAssignable>; - using P = std::pair<int, NonMoveAssignable>; - static_assert(!std::is_assignable<T&, P&&>::value, ""); + using T = std::tuple<int, NonAssignable>; + using P = std::pair<int, NonAssignable>; + static_assert(!std::is_assignable<T&, P&&>::value, ""); } { using T = std::tuple<int, int, int>; using P = std::pair<int, int>; static_assert(!std::is_assignable<T&, P&&>::value, ""); } + { + typedef std::tuple<NothrowMoveAssignable, long> Tuple; + typedef std::pair<NothrowMoveAssignable, int> Pair; + static_assert(std::is_nothrow_assignable<Tuple&, Pair&&>::value, ""); + static_assert(!std::is_assignable<Tuple&, Pair const&&>::value, ""); + } + { + typedef std::tuple<PotentiallyThrowingMoveAssignable, long> Tuple; + typedef std::pair<PotentiallyThrowingMoveAssignable, int> Pair; + static_assert(std::is_assignable<Tuple&, Pair&&>::value, ""); + static_assert(!std::is_nothrow_assignable<Tuple&, Pair&&>::value, ""); + static_assert(!std::is_assignable<Tuple&, Pair const&&>::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 <type_traits> + +#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 <class Tp> +using UnCVRef = + typename std::remove_cv<typename std::remove_reference<Tp>::type>::type; + +template <class Tp> +constexpr ValueCategory getReferenceQuals() { + return std::is_lvalue_reference<Tp>::value + ? VC_LVal + : (std::is_rvalue_reference<Tp>::value ? VC_RVal : VC_None); +} +static_assert(getReferenceQuals<int>() == VC_None, ""); +static_assert(getReferenceQuals<int &>() == VC_LVal, ""); +static_assert(getReferenceQuals<int &&>() == VC_RVal, ""); + +template <class Tp> +constexpr ValueCategory getCVQuals() { + using Vp = typename std::remove_reference<Tp>::type; + return std::is_const<Vp>::value && std::is_volatile<Vp>::value + ? VC_ConstVolatile + : (std::is_const<Vp>::value + ? VC_Const + : (std::is_volatile<Vp>::value ? VC_Volatile : VC_None)); +} +static_assert(getCVQuals<int>() == VC_None, ""); +static_assert(getCVQuals<int const>() == VC_Const, ""); +static_assert(getCVQuals<int volatile>() == VC_Volatile, ""); +static_assert(getCVQuals<int const volatile>() == VC_ConstVolatile, ""); +static_assert(getCVQuals<int &>() == VC_None, ""); +static_assert(getCVQuals<int const &>() == VC_Const, ""); + +template <class Tp> +inline constexpr ValueCategory getValueCategory() { + return getReferenceQuals<Tp>() | getCVQuals<Tp>(); +} +static_assert(getValueCategory<int>() == VC_None, ""); +static_assert(getValueCategory<int const &>() == (VC_LVal | VC_Const), ""); +static_assert(getValueCategory<int const volatile &&>() == + (VC_RVal | VC_ConstVolatile), + ""); + +template <ValueCategory VC> +struct ApplyValueCategory { +private: + static_assert(isValidValueCategory(VC), ""); + + template <bool Pred, class Then, class Else> + using CondT = typename std::conditional<Pred, Then, Else>::type; + +public: + template <class Tp, class Vp = UnCVRef<Tp>> + using ApplyCVQuals = CondT< + hasValueCategory(VC, VC_ConstVolatile), typename std::add_cv<Vp>::type, + CondT<hasValueCategory(VC, VC_Const), typename std::add_const<Vp>::type, + CondT<hasValueCategory(VC, VC_Volatile), + typename std::add_volatile<Vp>::type, Tp>>>; + + template <class Tp, class Vp = typename std::remove_reference<Tp>::type> + using ApplyReferenceQuals = + CondT<hasValueCategory(VC, VC_LVal), + typename std::add_lvalue_reference<Vp>::type, + CondT<hasValueCategory(VC, VC_RVal), + typename std::add_rvalue_reference<Vp>::type, Vp>>; + + template <class Tp> + using Apply = ApplyReferenceQuals<ApplyCVQuals<UnCVRef<Tp>>>; + + template <class Tp, bool Dummy = true, + typename std::enable_if<Dummy && (VC & VC_LVal), bool>::type = true> + static Apply<UnCVRef<Tp>> cast(Tp &&t) { + using ToType = Apply<UnCVRef<Tp>>; + return static_cast<ToType>(t); + } + + template <class Tp, bool Dummy = true, + typename std::enable_if<Dummy && (VC & VC_RVal), bool>::type = true> + static Apply<UnCVRef<Tp>> cast(Tp &&t) { + using ToType = Apply<UnCVRef<Tp>>; + return static_cast<ToType>(std::move(t)); + } + + template < + class Tp, bool Dummy = true, + typename std::enable_if<Dummy && ((VC & (VC_LVal | VC_RVal)) == VC_None), + bool>::type = true> + static Apply<UnCVRef<Tp>> cast(Tp &&t) { + return t; + } +}; + +template <ValueCategory VC, class Tp> +using ApplyValueCategoryT = typename ApplyValueCategory<VC>::template Apply<Tp>; + +template <class Tp> +using PropagateValueCategory = ApplyValueCategory<getValueCategory<Tp>()>; + +template <class Tp, class Up> +using PropagateValueCategoryT = + typename ApplyValueCategory<getValueCategory<Tp>()>::template Apply<Up>; + +template <ValueCategory VC, class Tp> +typename ApplyValueCategory<VC>::template Apply<Tp> ValueCategoryCast(Tp &&t) { + return ApplyValueCategory<VC>::cast(std::forward<Tp>(t)); +}; + +#endif // TEST_SUPPORT_PROPAGATE_VALUE_CATEGORY |