mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-23 05:40:09 +00:00
[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:
parent
946a09945f
commit
a0839b14df
@ -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
|
||||
>
|
||||
_LIBCPP_INLINE_VISIBILITY
|
||||
tuple&
|
||||
operator=(_Tuple&& __t) _NOEXCEPT_((is_nothrow_assignable<_BaseT&, _Tuple>::value))
|
||||
{
|
||||
__base_.operator=(_VSTD::forward<_Tuple>(__t));
|
||||
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=(tuple<_Up...> const& __tuple)
|
||||
_NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up const&>...>::value))
|
||||
{
|
||||
_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_);}
|
||||
|
@ -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;
|
||||
}
|
@ -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, "");
|
||||
|
||||
return 0;
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
153
libcxx/test/support/propagate_value_category.hpp
Normal file
153
libcxx/test/support/propagate_value_category.hpp
Normal 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
|
Loading…
Reference in New Issue
Block a user