forked from boostorg/intrusive
Fixes Trac #11994: Support intrusive container key extractors that return the key by value
Change tests to return key by value.
This commit is contained in:
@@ -3838,6 +3838,8 @@ to be inserted in intrusive containers are allocated using `std::vector` or `std
|
|||||||
[section:release_notes_boost_1_62_00 Boost 1.62 Release]
|
[section:release_notes_boost_1_62_00 Boost 1.62 Release]
|
||||||
|
|
||||||
* Fixed bugs:
|
* Fixed bugs:
|
||||||
|
* [@https://svn.boost.org/trac/boost/ticket/11994 Boost Trac #11994: ['Support intrusive container key extractors that return the key by value]]
|
||||||
|
* [@https://svn.boost.org/trac/boost/ticket/12184 Boost Trac #12184: ['clang -Wdocumentation warning]]
|
||||||
* [@https://svn.boost.org/trac/boost/ticket/12190 Boost Trac #12190: ['Intrusive List + Flat Map combination crashes]]
|
* [@https://svn.boost.org/trac/boost/ticket/12190 Boost Trac #12190: ['Intrusive List + Flat Map combination crashes]]
|
||||||
* [@https://svn.boost.org/trac/boost/ticket/12245 Boost Trac #12245: ['bstree uses a shared static size_traits for constant_time_size<false>]]
|
* [@https://svn.boost.org/trac/boost/ticket/12245 Boost Trac #12245: ['bstree uses a shared static size_traits for constant_time_size<false>]]
|
||||||
|
|
||||||
|
@@ -24,10 +24,14 @@
|
|||||||
#include <boost/intrusive/detail/mpl.hpp>
|
#include <boost/intrusive/detail/mpl.hpp>
|
||||||
#include <boost/intrusive/detail/ebo_functor_holder.hpp>
|
#include <boost/intrusive/detail/ebo_functor_holder.hpp>
|
||||||
|
|
||||||
|
|
||||||
namespace boost {
|
namespace boost {
|
||||||
namespace intrusive {
|
namespace intrusive {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
|
|
||||||
|
//This function object transforms a key comparison type to
|
||||||
|
//a function that can compare nodes or nodes with nodes or keys.
|
||||||
template < class KeyTypeKeyCompare
|
template < class KeyTypeKeyCompare
|
||||||
, class ValueTraits
|
, class ValueTraits
|
||||||
, class KeyOfValue = void
|
, class KeyOfValue = void
|
||||||
@@ -46,48 +50,63 @@ struct key_nodeptr_comp
|
|||||||
, detail::identity<value_type>
|
, detail::identity<value_type>
|
||||||
, KeyOfValue
|
, KeyOfValue
|
||||||
>::type key_of_value;
|
>::type key_of_value;
|
||||||
typedef typename key_of_value::type key_type;
|
|
||||||
|
|
||||||
key_nodeptr_comp(KeyTypeKeyCompare kcomp, const ValueTraits *traits)
|
template <class P1>
|
||||||
|
struct is_same_or_nodeptr_convertible
|
||||||
|
{
|
||||||
|
static const bool same_type = is_same<P1,const_node_ptr>::value || is_same<P1,node_ptr>::value;
|
||||||
|
static const bool value = same_type || is_convertible<P1, const_node_ptr>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOST_INTRUSIVE_FORCEINLINE key_nodeptr_comp(KeyTypeKeyCompare kcomp, const ValueTraits *traits)
|
||||||
: base_t(kcomp), traits_(traits)
|
: base_t(kcomp), traits_(traits)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
template<class T>
|
//pred(pnode)
|
||||||
struct is_node_ptr
|
template<class T1>
|
||||||
{
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, typename enable_if_c< is_same_or_nodeptr_convertible<T1>::value >::type* =0) const
|
||||||
static const bool value = is_same<T, const_node_ptr>::value || is_same<T, node_ptr>::value;
|
{ return base_t::get()(key_of_value()(*traits_->to_value_ptr(t1))); }
|
||||||
};
|
|
||||||
|
|
||||||
//key_forward
|
template<class T1>
|
||||||
template<class T>
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, typename enable_if_c< is_same_or_nodeptr_convertible<T1>::value >::type* =0)
|
||||||
typename enable_if<is_node_ptr<T>, const key_type &>::type key_forward(const T &node) const
|
{ return base_t::get()(key_of_value()(*traits_->to_value_ptr(t1))); }
|
||||||
{ return key_of_value()(*traits_->to_value_ptr(node)); }
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
#if defined(BOOST_MOVE_HELPERS_RETURN_SFINAE_BROKEN)
|
|
||||||
const T &key_forward (const T &key, typename disable_if<is_node_ptr<T> >::type* =0) const
|
|
||||||
#else
|
|
||||||
typename disable_if<is_node_ptr<T>, const T &>::type key_forward(const T &key) const
|
|
||||||
#endif
|
|
||||||
{ return key; }
|
|
||||||
|
|
||||||
//operator() 1 arg
|
|
||||||
template<class KeyType>
|
|
||||||
bool operator()(const KeyType &key1) const
|
|
||||||
{ return base_t::get()(this->key_forward(key1)); }
|
|
||||||
|
|
||||||
template<class KeyType>
|
|
||||||
bool operator()(const KeyType &key1)
|
|
||||||
{ return base_t::get()(this->key_forward(key1)); }
|
|
||||||
|
|
||||||
//operator() 2 arg
|
//operator() 2 arg
|
||||||
template<class KeyType, class KeyType2>
|
//pred(pnode, pnode)
|
||||||
bool operator()(const KeyType &key1, const KeyType2 &key2) const
|
template<class T1, class T2>
|
||||||
{ return base_t::get()(this->key_forward(key1), this->key_forward(key2)); }
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, const T2 &t2, typename enable_if_c< is_same_or_nodeptr_convertible<T1>::value && is_same_or_nodeptr_convertible<T2>::value >::type* =0) const
|
||||||
|
{ return base_t::get()(key_of_value()(*traits_->to_value_ptr(t1)), key_of_value()(*traits_->to_value_ptr(t2))); }
|
||||||
|
|
||||||
template<class KeyType, class KeyType2>
|
template<class T1, class T2>
|
||||||
bool operator()(const KeyType &key1, const KeyType2 &key2)
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, const T2 &t2, typename enable_if_c< is_same_or_nodeptr_convertible<T1>::value && is_same_or_nodeptr_convertible<T2>::value >::type* =0)
|
||||||
{ return base_t::get()(this->key_forward(key1), this->key_forward(key2)); }
|
{ return base_t::get()(key_of_value()(*traits_->to_value_ptr(t1)), key_of_value()(*traits_->to_value_ptr(t2))); }
|
||||||
|
|
||||||
|
//pred(pnode, key)
|
||||||
|
template<class T1, class T2>
|
||||||
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, const T2 &t2, typename enable_if_c< is_same_or_nodeptr_convertible<T1>::value && !is_same_or_nodeptr_convertible<T2>::value >::type* =0) const
|
||||||
|
{ return base_t::get()(key_of_value()(*traits_->to_value_ptr(t1)), t2); }
|
||||||
|
|
||||||
|
template<class T1, class T2>
|
||||||
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, const T2 &t2, typename enable_if_c< is_same_or_nodeptr_convertible<T1>::value && !is_same_or_nodeptr_convertible<T2>::value >::type* =0)
|
||||||
|
{ return base_t::get()(key_of_value()(*traits_->to_value_ptr(t1)), t2); }
|
||||||
|
|
||||||
|
//pred(key, pnode)
|
||||||
|
template<class T1, class T2>
|
||||||
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, const T2 &t2, typename enable_if_c< !is_same_or_nodeptr_convertible<T1>::value && is_same_or_nodeptr_convertible<T2>::value >::type* =0) const
|
||||||
|
{ return base_t::get()(t1, key_of_value()(*traits_->to_value_ptr(t2))); }
|
||||||
|
|
||||||
|
template<class T1, class T2>
|
||||||
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, const T2 &t2, typename enable_if_c< !is_same_or_nodeptr_convertible<T1>::value && is_same_or_nodeptr_convertible<T2>::value >::type* =0)
|
||||||
|
{ return base_t::get()(t1, key_of_value()(*traits_->to_value_ptr(t2))); }
|
||||||
|
|
||||||
|
//pred(key, key)
|
||||||
|
template<class T1, class T2>
|
||||||
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, const T2 &t2, typename enable_if_c< !is_same_or_nodeptr_convertible<T1>::value && !is_same_or_nodeptr_convertible<T2>::value >::type* =0) const
|
||||||
|
{ return base_t::get()(t1, t2); }
|
||||||
|
|
||||||
|
template<class T1, class T2>
|
||||||
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const T1 &t1, const T2 &t2, typename enable_if_c< !is_same_or_nodeptr_convertible<T1>::value && !is_same_or_nodeptr_convertible<T2>::value >::type* =0)
|
||||||
|
{ return base_t::get()(t1, t2); }
|
||||||
|
|
||||||
const ValueTraits *const traits_;
|
const ValueTraits *const traits_;
|
||||||
};
|
};
|
||||||
|
@@ -25,6 +25,9 @@
|
|||||||
namespace boost{
|
namespace boost{
|
||||||
namespace intrusive{
|
namespace intrusive{
|
||||||
|
|
||||||
|
|
||||||
|
//This function object takes a KeyCompare function object
|
||||||
|
//and compares values that contains keys using KeyOfValue
|
||||||
template<class Key, class T, class KeyCompare, class KeyOfValue>
|
template<class Key, class T, class KeyCompare, class KeyOfValue>
|
||||||
struct tree_value_compare
|
struct tree_value_compare
|
||||||
: public boost::intrusive::detail::ebo_functor_holder<KeyCompare>
|
: public boost::intrusive::detail::ebo_functor_holder<KeyCompare>
|
||||||
@@ -35,23 +38,22 @@ struct tree_value_compare
|
|||||||
typedef KeyOfValue key_of_value;
|
typedef KeyOfValue key_of_value;
|
||||||
typedef Key key_type;
|
typedef Key key_type;
|
||||||
|
|
||||||
|
BOOST_INTRUSIVE_FORCEINLINE tree_value_compare()
|
||||||
tree_value_compare()
|
|
||||||
: base_t()
|
: base_t()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
explicit tree_value_compare(const key_compare &kcomp)
|
BOOST_INTRUSIVE_FORCEINLINE explicit tree_value_compare(const key_compare &kcomp)
|
||||||
: base_t(kcomp)
|
: base_t(kcomp)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
tree_value_compare (const tree_value_compare &x)
|
BOOST_INTRUSIVE_FORCEINLINE tree_value_compare (const tree_value_compare &x)
|
||||||
: base_t(x.base_t::get())
|
: base_t(x.base_t::get())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
tree_value_compare &operator=(const tree_value_compare &x)
|
BOOST_INTRUSIVE_FORCEINLINE tree_value_compare &operator=(const tree_value_compare &x)
|
||||||
{ this->base_t::get() = x.base_t::get(); return *this; }
|
{ this->base_t::get() = x.base_t::get(); return *this; }
|
||||||
|
|
||||||
tree_value_compare &operator=(const key_compare &x)
|
BOOST_INTRUSIVE_FORCEINLINE tree_value_compare &operator=(const key_compare &x)
|
||||||
{ this->base_t::get() = x; return *this; }
|
{ this->base_t::get() = x; return *this; }
|
||||||
|
|
||||||
BOOST_INTRUSIVE_FORCEINLINE const key_compare &key_comp() const
|
BOOST_INTRUSIVE_FORCEINLINE const key_compare &key_comp() const
|
||||||
@@ -65,23 +67,13 @@ struct tree_value_compare
|
|||||||
: boost::intrusive::detail::is_same<const U, const key_type>
|
: boost::intrusive::detail::is_same<const U, const key_type>
|
||||||
{};
|
{};
|
||||||
|
|
||||||
template<class U>
|
template<class U, class V>
|
||||||
const key_type & key_forward
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const U &key1, const V &key2) const
|
||||||
(const U &key, typename boost::intrusive::detail::enable_if<is_key<U> >::type* = 0) const
|
{ return key_compare::operator()(KeyOfValue()(key1), KeyOfValue()(key2)); }
|
||||||
{ return key; }
|
|
||||||
|
|
||||||
template<class U>
|
template<class U, class V>
|
||||||
BOOST_INTRUSIVE_FORCEINLINE const key_type & key_forward
|
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const U &key1, const V &key2)
|
||||||
(const U &key, typename boost::intrusive::detail::disable_if<is_key<U> >::type* = 0) const
|
{ return key_compare::operator()(KeyOfValue()(key1), KeyOfValue()(key2)); }
|
||||||
{ return KeyOfValue()(key); }
|
|
||||||
|
|
||||||
template<class KeyType, class KeyType2>
|
|
||||||
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const KeyType &key1, const KeyType2 &key2) const
|
|
||||||
{ return key_compare::operator()(this->key_forward(key1), this->key_forward(key2)); }
|
|
||||||
|
|
||||||
template<class KeyType, class KeyType2>
|
|
||||||
BOOST_INTRUSIVE_FORCEINLINE bool operator()(const KeyType &key1, const KeyType2 &key2)
|
|
||||||
{ return key_compare::operator()(this->key_forward(key1), this->key_forward(key2)); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} //namespace intrusive{
|
} //namespace intrusive{
|
||||||
|
@@ -61,12 +61,12 @@ BOOST_INTRUSIVE_OPTION_TYPE(size_type, SizeType, SizeType, size_type)
|
|||||||
//!comparison functor for the value type
|
//!comparison functor for the value type
|
||||||
BOOST_INTRUSIVE_OPTION_TYPE(compare, Compare, Compare, compare)
|
BOOST_INTRUSIVE_OPTION_TYPE(compare, Compare, Compare, compare)
|
||||||
|
|
||||||
//!This option setter specifies the a function object
|
//!This option setter specifies a function object
|
||||||
//!that specifies the type of the key of an associative
|
//!that specifies the type of the key of an associative
|
||||||
//!container and an operator to obtain it from a value type.
|
//!container and an operator to obtain it from a value type.
|
||||||
//!
|
//!
|
||||||
//!This function object must the define a `key_type` and
|
//!This function object must the define a `type` member typedef and
|
||||||
//!a member with signature `const key_type & operator()(const value_type &) const`
|
//!a member with signature `type [const&] operator()(const value_type &) const`
|
||||||
//!that will return the key from a value_type of an associative container
|
//!that will return the key from a value_type of an associative container
|
||||||
BOOST_INTRUSIVE_OPTION_TYPE(key_of_value, KeyOfValue, KeyOfValue, key_of_value)
|
BOOST_INTRUSIVE_OPTION_TYPE(key_of_value, KeyOfValue, KeyOfValue, key_of_value)
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ BOOST_INTRUSIVE_OPTION_CONSTANT(floating_point, bool, Enabled, floating_point)
|
|||||||
//!functor for the value type
|
//!functor for the value type
|
||||||
BOOST_INTRUSIVE_OPTION_TYPE(equal, Equal, Equal, equal)
|
BOOST_INTRUSIVE_OPTION_TYPE(equal, Equal, Equal, equal)
|
||||||
|
|
||||||
//!This option setter specifies the equality
|
//!This option setter specifies the priority comparison
|
||||||
//!functor for the value type
|
//!functor for the value type
|
||||||
BOOST_INTRUSIVE_OPTION_TYPE(priority, Priority, Priority, priority)
|
BOOST_INTRUSIVE_OPTION_TYPE(priority, Priority, Priority, priority)
|
||||||
|
|
||||||
|
@@ -96,7 +96,7 @@ struct int_holder_key_of_value
|
|||||||
{
|
{
|
||||||
typedef int_holder type;
|
typedef int_holder type;
|
||||||
|
|
||||||
const type &operator()(const ValueType &tv)
|
type operator()(const ValueType &tv)
|
||||||
{ return tv.get_int_holder(); }
|
{ return tv.get_int_holder(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -132,18 +132,18 @@ template <class Type>
|
|||||||
bool priority_order(const Type& t1, const Type& t2)
|
bool priority_order(const Type& t1, const Type& t2)
|
||||||
{
|
{
|
||||||
std::size_t hash1 = boost::hash<int>()((&t1)->int_value());
|
std::size_t hash1 = boost::hash<int>()((&t1)->int_value());
|
||||||
boost::hash_combine(hash1, &t1);
|
boost::hash_combine(hash1, -hash1);
|
||||||
std::size_t hash2 = boost::hash<int>()((&t2)->int_value());
|
std::size_t hash2 = boost::hash<int>()((&t2)->int_value());
|
||||||
boost::hash_combine(hash2, &t2);
|
boost::hash_combine(hash2, -hash2);
|
||||||
return hash1 < hash2;
|
return hash1 < hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool priority_order(int t1, int t2)
|
bool priority_order(int t1, int t2)
|
||||||
{
|
{
|
||||||
std::size_t hash1 = boost::hash<int>()(t1);
|
std::size_t hash1 = boost::hash<int>()(t1);
|
||||||
boost::hash_combine(hash1, &t1);
|
boost::hash_combine(hash1, -hash1);
|
||||||
std::size_t hash2 = boost::hash<int>()(t2);
|
std::size_t hash2 = boost::hash<int>()(t2);
|
||||||
boost::hash_combine(hash2, &t2);
|
boost::hash_combine(hash2, -hash2);
|
||||||
return hash1 < hash2;
|
return hash1 < hash2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -761,98 +761,3 @@ void test_unordered<ValueTraits, ContainerDefiner>
|
|||||||
} //namespace test{
|
} //namespace test{
|
||||||
} //namespace intrusive{
|
} //namespace intrusive{
|
||||||
} //namespace boost{
|
} //namespace boost{
|
||||||
/*
|
|
||||||
template < class ValueTraits, bool ConstantTimeSize, bool CacheBegin, bool CompareHash, bool Incremental, bool Map, bool DefaultHolder >
|
|
||||||
struct rebinder
|
|
||||||
{
|
|
||||||
typedef unordered_rebinder_common<ValueTraits, DefaultHolder, Map> common_t;
|
|
||||||
|
|
||||||
template < class Option1 =void
|
|
||||||
, class Option2 =void
|
|
||||||
>
|
|
||||||
struct container
|
|
||||||
{
|
|
||||||
typedef unordered_multiset
|
|
||||||
< typename common_t::value_type
|
|
||||||
, value_traits<ValueTraits>
|
|
||||||
, constant_time_size<ConstantTimeSize>
|
|
||||||
, cache_begin<CacheBegin>
|
|
||||||
, compare_hash<CompareHash>
|
|
||||||
, incremental<Incremental>
|
|
||||||
, typename common_t::holder_opt
|
|
||||||
, typename common_t::key_of_value_opt
|
|
||||||
, Option1
|
|
||||||
, Option2
|
|
||||||
> type;
|
|
||||||
BOOST_STATIC_ASSERT((key_type_tester<typename common_t::key_of_value_opt, type>::value));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class VoidPointer, bool ConstantTimeSize, bool Map, bool DefaultHolder>
|
|
||||||
class test_main_template
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static void execute()
|
|
||||||
{
|
|
||||||
typedef testvalue<unordered_hooks<VoidPointer> > value_type;
|
|
||||||
static const int random_init[6] = { 3, 2, 4, 1, 5, 2 };
|
|
||||||
typedef typename ValueContainer< value_type >::type value_cont_type;
|
|
||||||
value_cont_type data (6);
|
|
||||||
for (int i = 0; i < 6; ++i)
|
|
||||||
data[i].value_ = random_init[i];
|
|
||||||
|
|
||||||
typedef testvalue_traits< unordered_hooks<VoidPointer> > testval_traits_t;
|
|
||||||
//base
|
|
||||||
typedef typename detail::if_c
|
|
||||||
< ConstantTimeSize
|
|
||||||
, typename testval_traits_t::base_value_traits
|
|
||||||
, typename testval_traits_t::auto_base_value_traits //store_hash<true>
|
|
||||||
>::type base_hook_t;
|
|
||||||
test::test_unordered_multiset
|
|
||||||
< base_hook_t //cache_begin, compare_hash, incremental
|
|
||||||
, rebinder<base_hook_t, ConstantTimeSize, ConstantTimeSize, !ConstantTimeSize, !!ConstantTimeSize, Map, DefaultHolder>
|
|
||||||
>::test_all(data);
|
|
||||||
//member
|
|
||||||
typedef typename detail::if_c
|
|
||||||
< ConstantTimeSize
|
|
||||||
, typename testval_traits_t::member_value_traits //optimize_multikey<true>
|
|
||||||
, typename testval_traits_t::auto_member_value_traits //store_hash<true>, optimize_multikey<true>
|
|
||||||
>::type member_hook_t;
|
|
||||||
test::test_unordered_multiset
|
|
||||||
< member_hook_t //cache_begin, compare_hash, incremental
|
|
||||||
, rebinder<member_hook_t, ConstantTimeSize, false, !ConstantTimeSize, false, !ConstantTimeSize, DefaultHolder>
|
|
||||||
>::test_all(data);
|
|
||||||
//nonmember
|
|
||||||
test::test_unordered_multiset
|
|
||||||
< typename testval_traits_t::nonhook_value_traits //cache_begin, compare_hash, incremental
|
|
||||||
, rebinder<typename testval_traits_t::nonhook_value_traits, ConstantTimeSize, false, false, false, Map, DefaultHolder>
|
|
||||||
>::test_all(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
//VoidPointer x ConstantTimeSize x Map x DefaultHolder
|
|
||||||
|
|
||||||
//void pointer
|
|
||||||
test_main_template<void*, false, false, false>::execute();
|
|
||||||
test_main_template<void*, false, true, false>::execute();
|
|
||||||
test_main_template<void*, true, false, false>::execute();
|
|
||||||
test_main_template<void*, true, true, false>::execute();
|
|
||||||
|
|
||||||
//smart_ptr
|
|
||||||
test_main_template<smart_ptr<void>, false, false, false>::execute();
|
|
||||||
test_main_template<smart_ptr<void>, false, true, false>::execute();
|
|
||||||
test_main_template<smart_ptr<void>, true, false, false>::execute();
|
|
||||||
test_main_template<smart_ptr<void>, true, true, false>::execute();
|
|
||||||
|
|
||||||
////bounded_ptr (bool ConstantTimeSize, bool Map)
|
|
||||||
//test_main_template_bptr< false, false >::execute();
|
|
||||||
//test_main_template_bptr< false, true >::execute();
|
|
||||||
//test_main_template_bptr< true, false >::execute();
|
|
||||||
test_main_template_bptr< true, true >::execute();
|
|
||||||
|
|
||||||
return boost::report_errors();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user