ldionne updated this revision to Diff 322746. ldionne retitled this revision from "[libc++] Fix tuple assignment from type derived from a tuple-like" to "[libc++] Fix tuple assignment from types derived from a tuple-like". ldionne edited the summary of this revision. ldionne added a comment. Herald added a project: libc++. Herald added a reviewer: libc++.
Fix issues with the laziness of how we evaluate type traits Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D50106/new/ https://reviews.llvm.org/D50106 Files: libcxx/include/tuple libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/const_array.pass.cpp libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/rvalue_array.pass.cpp libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp libcxx/test/support/propagate_value_category.hpp
Index: libcxx/test/support/propagate_value_category.hpp =================================================================== --- /dev/null +++ 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 Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp =================================================================== --- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp +++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp @@ -37,6 +37,16 @@ explicit D(int i) : B(i) {} }; +struct NothrowMoveAssignable +{ + NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; } +}; + +struct PotentiallyThrowingMoveAssignable +{ + PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; } +}; + int main(int, char**) { { @@ -48,6 +58,16 @@ assert(std::get<0>(t1) == 2); assert(std::get<1>(t1)->id_ == 3); } + { + typedef std::tuple<NothrowMoveAssignable, long> Tuple; + typedef std::pair<NothrowMoveAssignable, int> Pair; + static_assert(std::is_nothrow_assignable<Tuple&, Pair&&>::value, ""); + } + { + typedef std::tuple<PotentiallyThrowingMoveAssignable, long> Tuple; + typedef std::pair<PotentiallyThrowingMoveAssignable, int> Pair; + static_assert(!std::is_nothrow_assignable<Tuple&, Pair&&>::value, ""); + } - return 0; + return 0; } Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp =================================================================== --- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp +++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp @@ -35,7 +35,12 @@ 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 @@ int CountAssign::copied = 0; int CountAssign::moved = 0; - int main(int, char**) { { @@ -102,15 +106,6 @@ 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>; - static_assert(!std::is_move_assignable<T>::value, ""); - } - { - using T = std::tuple<int, MoveAssignable>; - static_assert(std::is_move_assignable<T>::value, ""); } { // The move should decay to a copy. @@ -123,6 +118,22 @@ 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; } Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.pass.cpp =================================================================== --- /dev/null +++ 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; +} Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp =================================================================== --- /dev/null +++ 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; +} Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp =================================================================== --- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp +++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp @@ -34,6 +34,12 @@ 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; } +}; int main(int, char**) { @@ -100,6 +106,14 @@ using T = std::tuple<int, MoveAssignable>; static_assert(!std::is_copy_assignable<T>::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; } Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp =================================================================== --- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp +++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp @@ -45,6 +45,16 @@ } }; +struct NothrowMoveAssignable +{ + NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; } +}; + +struct PotentiallyThrowingMoveAssignable +{ + PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; } +}; + int main(int, char**) { { @@ -108,6 +118,16 @@ assert(std::get<0>(t) == 43); assert(&std::get<0>(t) == &x); } + { + 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; } Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp =================================================================== --- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp +++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp @@ -34,6 +34,16 @@ explicit D(int i = 0) : B(i) {} }; +struct NothrowCopyAssignable +{ + NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; } +}; + +struct PotentiallyThrowingCopyAssignable +{ + PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; } +}; + int main(int, char**) { { @@ -87,6 +97,16 @@ assert(std::get<0>(t) == 43); assert(&std::get<0>(t) == &x); } + { + 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; } Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp =================================================================== --- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp +++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp @@ -15,9 +15,18 @@ // UNSUPPORTED: c++03 +#include <cassert> #include <tuple> +#include <type_traits> #include <utility> -#include <cassert> + + +struct NothrowCopyAssignable { + NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; } +}; +struct PotentiallyThrowingCopyAssignable { + PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; } +}; #include "test_macros.h" @@ -32,6 +41,16 @@ assert(std::get<0>(t1) == 2); assert(std::get<1>(t1) == short('a')); } + { + typedef std::tuple<NothrowCopyAssignable, long> Tuple; + typedef std::pair<NothrowCopyAssignable, int> Pair; + 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_nothrow_assignable<Tuple&, Pair const&>::value, ""); + } - return 0; + return 0; } Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp =================================================================== --- /dev/null +++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp @@ -1,37 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 - -// <tuple> - -// template <class... Types> class tuple; - -// template <class Tuple, __tuple_assignable<Tuple, tuple> > -// tuple & operator=(Tuple &&); - -// This test checks that we do not evaluate __make_tuple_types -// on the array when it doesn't match the size of the tuple. - -#include <array> -#include <tuple> - -#include "test_macros.h" - -// Use 1256 to try and blow the template instantiation depth for all compilers. -typedef std::array<char, 1256> array_t; -typedef std::tuple<array_t> tuple_t; - -int main(int, char**) -{ - array_t arr; - tuple_t tup; - tup = arr; - - return 0; -} Index: libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/rvalue_array.pass.cpp =================================================================== --- /dev/null +++ libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/rvalue_array.pass.cpp @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// 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=(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**) { + { + 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_nothrow_assignable<Tuple&, Array&&>::value, ""); + } + + return 0; +} Index: libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/const_array.pass.cpp =================================================================== --- /dev/null +++ libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/const_array.pass.cpp @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// 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); + +// 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**) { + { + 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_nothrow_assignable<Tuple&, Array const&>::value, ""); + } + + return 0; +} Index: libcxx/include/tuple =================================================================== --- libcxx/include/tuple +++ libcxx/include/tuple @@ -55,8 +55,7 @@ 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(AND(is_nothrow_move_assignable<T>::value ...)); template <class... U> tuple& operator=(const tuple<U...>&); template <class... U> @@ -66,6 +65,11 @@ 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 @@ __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 @@ __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,40 +415,9 @@ 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) @@ -471,7 +426,19 @@ } }; +template<class _Dest, class _Source, size_t ..._Np> +_LIBCPP_INLINE_VISIBILITY +void __memberwise_copy_assign(_Dest& __dest, _Source const& __source, __tuple_indices<_Np...>) { + __swallow(((_VSTD::get<_Np>(__dest) = _VSTD::get<_Np>(__source)), void(), int())...); +} +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...>) { + __swallow((( + _VSTD::get<_Np>(__dest) = _VSTD::forward<_Up>(_VSTD::get<_Np>(_VSTD::forward<_Source>(__source))) + ), void(), int())...); +} template <class ..._Tp> class _LIBCPP_TEMPLATE_VIS tuple @@ -916,39 +883,123 @@ 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)) + { + __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)) + { + __memberwise_forward_assign(*this, _VSTD::move(__tuple), + __tuple_types<_Tp...>(), + typename __make_tuple_indices<sizeof...(_Tp)>::type()); + return *this; + } + template<class... _Up, class = _EnableIf< + _And< + _BoolConstant<sizeof...(_Tp) == sizeof...(_Up)>, + is_assignable<_Tp&, _Up const&>... + >::value + > > _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_); + __memberwise_copy_assign(*this, __tuple, + typename __make_tuple_indices<sizeof...(_Tp)>::type()); return *this; } + template<class... _Up, class = _EnableIf< + _And< + _BoolConstant<sizeof...(_Tp) == sizeof...(_Up)>, + is_assignable<_Tp&, _Up&&>... + >::value + > > _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_)); + __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 _MakeDep = _Up1, size_t _Two = 2, class = _EnableIf< + _And< + _BoolConstant<sizeof...(_Tp) == _Two>, // this needs to be dependent + is_assignable<_FirstType<_Tp..., _MakeDep>&, _Up1 const&>, + is_assignable<_SecondType<_Tp..., _MakeDep>&, _Up2 const&> + >::value + > > + _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 _MakeDep = _Up1, size_t _Two = 2, class = _EnableIf< + _And< + _BoolConstant<sizeof...(_Tp) == _Two>, // this needs to be dependent + is_assignable<_FirstType<_Tp..., _MakeDep>&, _Up1&&>, + is_assignable<_SecondType<_Tp..., _MakeDep>&, _Up2&&> + >::value + > > + _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::forward<_Up1>(__pair.first); + _VSTD::get<1>(*this) = _VSTD::forward<_Up2>(__pair.second); + return *this; + } + + // EXTENSION + template<class _Up, class = _EnableIf< + _And<is_assignable<_Tp&, _Up const&>...>::value + > > + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(array<_Up, sizeof...(_Tp)> const& __array) + _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up const&>...>::value)) + { + __memberwise_copy_assign(*this, __array, + typename __make_tuple_indices<sizeof...(_Tp)>::type()); + return *this; + } + + // EXTENSION + template<class _Up, class = _EnableIf< + _And<is_assignable<_Tp&, _Up&&>...>::value + > > + _LIBCPP_INLINE_VISIBILITY + tuple& operator=(array<_Up, sizeof...(_Tp)>&& __array) + _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up&&>...>::value)) + { + __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_);}
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits