[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
This commit is contained in:
Louis Dionne 2018-07-31 11:56:20 -04:00 committed by Louis Dionne
parent 946a09945f
commit a0839b14df
12 changed files with 727 additions and 92 deletions

View File

@ -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=(typename conditional<_CanCopyAssign::value, tuple, __nat>::type const& __t)
_NOEXCEPT_((__all<is_nothrow_copy_assignable<_Tp>::value...>::value))
tuple& operator=(_If<_And<is_copy_assignable<_Tp>...>::value, tuple, __nat> const& __tuple)
_NOEXCEPT_((_And<is_nothrow_copy_assignable<_Tp>...>::value))
{
__base_.operator=(__t.__base_);
_VSTD::__memberwise_copy_assign(*this, __tuple,
typename __make_tuple_indices<sizeof...(_Tp)>::type());
return *this;
}
_LIBCPP_INLINE_VISIBILITY
tuple& operator=(typename conditional<_CanMoveAssign::value, tuple, __nat>::type&& __t)
_NOEXCEPT_((__all<is_nothrow_move_assignable<_Tp>::value...>::value))
tuple& operator=(_If<_And<is_move_assignable<_Tp>...>::value, tuple, __nat>&& __tuple)
_NOEXCEPT_((_And<is_nothrow_move_assignable<_Tp>...>::value))
{
__base_.operator=(static_cast<_BaseT&&>(__t.__base_));
_VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple),
__tuple_types<_Tp...>(),
typename __make_tuple_indices<sizeof...(_Tp)>::type());
return *this;
}
template <class _Tuple,
class = typename enable_if
<
__tuple_assignable<_Tuple, tuple>::value
>::type
>
template<class... _Up, _EnableIf<
_And<
_BoolConstant<sizeof...(_Tp) == sizeof...(_Up)>,
is_assignable<_Tp&, _Up const&>...
>::value
,int> = 0>
_LIBCPP_INLINE_VISIBILITY
tuple&
operator=(_Tuple&& __t) _NOEXCEPT_((is_nothrow_assignable<_BaseT&, _Tuple>::value))
tuple& operator=(tuple<_Up...> const& __tuple)
_NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up const&>...>::value))
{
__base_.operator=(_VSTD::forward<_Tuple>(__t));
_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=(tuple<_Up...>&& __tuple)
_NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up>...>::value))
{
_VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple),
__tuple_types<_Up...>(),
typename __make_tuple_indices<sizeof...(_Tp)>::type());
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_);}

View File

@ -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;
}

View File

@ -15,10 +15,18 @@
// UNSUPPORTED: c++03
#include <tuple>
#include <utility>
#include <memory>
#include <cassert>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,8 +65,8 @@ int main(int, char**)
assert(std::get<1>(t1)->id_ == 3);
}
{
using T = std::tuple<int, NonMoveAssignable>;
using P = std::pair<int, NonMoveAssignable>;
using T = std::tuple<int, NonAssignable>;
using P = std::pair<int, NonAssignable>;
static_assert(!std::is_assignable<T&, P&&>::value, "");
}
{
@ -63,6 +74,19 @@ int main(int, char**)
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;
}

View File

@ -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