Merge branch 'feature/simpler-data-structure' into develop

C++17 requires that unordered_map has the same type of node as
unordered_multimap, and that unordered_set has the same type of node as
unordered_multiset. This didn't seem particularly useful to me and
contradicts the old implementation which had different nodes, I put a
lot of effort into trying to abstract out the difference and make it
selectable using a macro, so that the old implementation would still by
available for anyone who doesn't care about strict compatibility.

But I think that was a mistake, it was making things too complicated and
for too little gain. The default would still be inefficient containers
for equivalent keys, and using the macro could lead to problems down the
line.

So I've switched to using a much simpler implementation which just marks
the first node in a group of equivalent nodes. This isn't as fast when
there are a lot of elements with equivalent keys - it can't skip to the
end of a group of nodes, but at least it avoids having to do a lot of
potentially expensive comparisons.

It's also a lot closer to the intent of the standard, even if I disagree
with that intent.
This commit is contained in:
Daniel James
2017-04-27 18:22:53 +01:00
10 changed files with 345 additions and 668 deletions

View File

@ -38,14 +38,6 @@ matrix:
env: |
label="clang 32 bit";
user_config="using clang : : clang++ -m32 -Werror --std=c++03 ;"
- compiler: gcc
env: |
label="gcc C++03 interopable1";
user_config="using gcc : : g++-4.8 -fsanitize=address -Werror --std=c++03 -DBOOST_UNORDERED_INTEROPERABLE_NODES=1 ;"
- compiler: clang
env: |
label="gcc C++11 interopable2";
user_config="using clang : : clang++ -fsanitize=address -Werror --std=c++03 -DBOOST_UNORDERED_INTEROPERABLE_NODES=2 ;"
before_script:
- cd ${TRAVIS_BUILD_DIR}

File diff suppressed because it is too large Load Diff

View File

@ -23,22 +23,13 @@ template <typename A, typename K, typename M, typename H, typename P> struct map
typedef boost::unordered::detail::allocator_traits<value_allocator>
value_allocator_traits;
#if BOOST_UNORDERED_INTEROPERABLE_NODES != 2
typedef boost::unordered::detail::pick_node<A, value_type> pick;
#else
typedef boost::unordered::detail::pick_grouped_node<A, value_type> pick;
#endif
typedef typename pick::node node;
typedef typename pick::bucket bucket;
typedef typename pick::link_pointer link_pointer;
typedef typename pick::node_algo node_algo;
typedef boost::unordered::detail::table<types> table;
typedef boost::unordered::detail::map_extractor<value_type> extractor;
enum
{
is_unique = true
};
typedef typename boost::unordered::detail::pick_policy<K>::type policy;
@ -54,50 +45,6 @@ template <typename A, typename K, typename M, typename H, typename P> struct map
insert_return_type;
};
template <typename A, typename K, typename M, typename H, typename P>
struct multimap
{
typedef boost::unordered::detail::multimap<A, K, M, H, P> types;
typedef std::pair<K const, M> value_type;
typedef H hasher;
typedef P key_equal;
typedef K const const_key_type;
typedef typename ::boost::unordered::detail::rebind_wrap<A,
value_type>::type value_allocator;
typedef boost::unordered::detail::allocator_traits<value_allocator>
value_allocator_traits;
#if BOOST_UNORDERED_INTEROPERABLE_NODES != 1
typedef boost::unordered::detail::pick_grouped_node<A, value_type> pick;
#else
typedef boost::unordered::detail::pick_node<A, value_type> pick;
#endif
typedef typename pick::node node;
typedef typename pick::bucket bucket;
typedef typename pick::link_pointer link_pointer;
typedef typename pick::node_algo node_algo;
typedef boost::unordered::detail::table<types> table;
typedef boost::unordered::detail::map_extractor<value_type> extractor;
enum
{
is_unique = false
};
typedef typename boost::unordered::detail::pick_policy<K>::type policy;
typedef boost::unordered::iterator_detail::iterator<node> iterator;
typedef boost::unordered::iterator_detail::c_iterator<node> c_iterator;
typedef boost::unordered::iterator_detail::l_iterator<node, policy>
l_iterator;
typedef boost::unordered::iterator_detail::cl_iterator<node, policy>
cl_iterator;
typedef boost::unordered::node_handle_map<node, K, M, A> node_type;
};
template <typename K, typename M, typename H, typename P, typename A>
class instantiate_map
{

View File

@ -23,22 +23,13 @@ template <typename A, typename T, typename H, typename P> struct set
typedef boost::unordered::detail::allocator_traits<value_allocator>
value_allocator_traits;
#if BOOST_UNORDERED_INTEROPERABLE_NODES != 2
typedef boost::unordered::detail::pick_node<A, value_type> pick;
#else
typedef boost::unordered::detail::pick_grouped_node<A, value_type> pick;
#endif
typedef typename pick::node node;
typedef typename pick::bucket bucket;
typedef typename pick::link_pointer link_pointer;
typedef typename pick::node_algo node_algo;
typedef boost::unordered::detail::table<types> table;
typedef boost::unordered::detail::set_extractor<value_type> extractor;
enum
{
is_unique = true
};
typedef typename boost::unordered::detail::pick_policy<T>::type policy;
@ -54,49 +45,6 @@ template <typename A, typename T, typename H, typename P> struct set
insert_return_type;
};
template <typename A, typename T, typename H, typename P> struct multiset
{
typedef boost::unordered::detail::multiset<A, T, H, P> types;
typedef T value_type;
typedef H hasher;
typedef P key_equal;
typedef T const const_key_type;
typedef typename ::boost::unordered::detail::rebind_wrap<A,
value_type>::type value_allocator;
typedef boost::unordered::detail::allocator_traits<value_allocator>
value_allocator_traits;
#if BOOST_UNORDERED_INTEROPERABLE_NODES != 1
typedef boost::unordered::detail::pick_grouped_node<A, value_type> pick;
#else
typedef boost::unordered::detail::pick_node<A, value_type> pick;
#endif
typedef typename pick::node node;
typedef typename pick::bucket bucket;
typedef typename pick::link_pointer link_pointer;
typedef typename pick::node_algo node_algo;
typedef boost::unordered::detail::table<types> table;
typedef boost::unordered::detail::set_extractor<value_type> extractor;
enum
{
is_unique = false
};
typedef typename boost::unordered::detail::pick_policy<T>::type policy;
typedef boost::unordered::iterator_detail::c_iterator<node> iterator;
typedef boost::unordered::iterator_detail::c_iterator<node> c_iterator;
typedef boost::unordered::iterator_detail::cl_iterator<node, policy>
l_iterator;
typedef boost::unordered::iterator_detail::cl_iterator<node, policy>
cl_iterator;
typedef boost::unordered::node_handle_set<node, T, A> node_type;
};
template <typename T, typename H, typename P, typename A> class instantiate_set
{
typedef boost::unordered_set<T, H, P, A> container;

View File

@ -146,7 +146,7 @@ template <class K, class T, class H, class P, class A> class unordered_map
#if defined(BOOST_UNORDERED_USE_MOVE)
unordered_map& operator=(BOOST_COPY_ASSIGN_REF(unordered_map) x)
{
table_.assign(x.table_);
table_.assign(x.table_, true);
return *this;
}
@ -156,13 +156,13 @@ template <class K, class T, class H, class P, class A> class unordered_map
// is_nothrow_move_assignable_v<H> &&
// is_nothrow_move_assignable_v<P>)
{
table_.move_assign(x.table_);
table_.move_assign(x.table_, true);
return *this;
}
#else
unordered_map& operator=(unordered_map const& x)
{
table_.assign(x.table_);
table_.assign(x.table_, true);
return *this;
}
@ -173,7 +173,7 @@ template <class K, class T, class H, class P, class A> class unordered_map
// is_nothrow_move_assignable_v<H> &&
// is_nothrow_move_assignable_v<P>)
{
table_.move_assign(x.table_);
table_.move_assign(x.table_, true);
return *this;
}
#endif
@ -740,14 +740,12 @@ template <class K, class T, class H, class P, class A> class unordered_map
void merge(boost::unordered_map<K, T, H2, P2, A>&& source);
#endif
#if BOOST_UNORDERED_INTEROPERABLE_NODES
template <typename H2, typename P2>
void merge(boost::unordered_multimap<K, T, H2, P2, A>& source);
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
template <typename H2, typename P2>
void merge(boost::unordered_multimap<K, T, H2, P2, A>&& source);
#endif
#endif
// observers
@ -858,7 +856,7 @@ template <class K, class T, class H, class P, class A> class unordered_multimap
typedef A allocator_type;
private:
typedef boost::unordered::detail::multimap<A, K, T, H, P> types;
typedef boost::unordered::detail::map<A, K, T, H, P> types;
typedef typename types::value_allocator_traits value_allocator_traits;
typedef typename types::table table;
typedef typename table::node_pointer node_pointer;
@ -952,7 +950,7 @@ template <class K, class T, class H, class P, class A> class unordered_multimap
#if defined(BOOST_UNORDERED_USE_MOVE)
unordered_multimap& operator=(BOOST_COPY_ASSIGN_REF(unordered_multimap) x)
{
table_.assign(x.table_);
table_.assign(x.table_, false);
return *this;
}
@ -962,13 +960,13 @@ template <class K, class T, class H, class P, class A> class unordered_multimap
// is_nothrow_move_assignable_v<H> &&
// is_nothrow_move_assignable_v<P>)
{
table_.move_assign(x.table_);
table_.move_assign(x.table_, false);
return *this;
}
#else
unordered_multimap& operator=(unordered_multimap const& x)
{
table_.assign(x.table_);
table_.assign(x.table_, false);
return *this;
}
@ -979,7 +977,7 @@ template <class K, class T, class H, class P, class A> class unordered_multimap
// is_nothrow_move_assignable_v<H> &&
// is_nothrow_move_assignable_v<P>)
{
table_.move_assign(x.table_);
table_.move_assign(x.table_, false);
return *this;
}
#endif
@ -1277,14 +1275,12 @@ template <class K, class T, class H, class P, class A> class unordered_multimap
void merge(boost::unordered_multimap<K, T, H2, P2, A>&& source);
#endif
#if BOOST_UNORDERED_INTEROPERABLE_NODES
template <typename H2, typename P2>
void merge(boost::unordered_map<K, T, H2, P2, A>& source);
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
template <typename H2, typename P2>
void merge(boost::unordered_map<K, T, H2, P2, A>&& source);
#endif
#endif
// observers
@ -1573,7 +1569,7 @@ unordered_map<K, T, H, P, A>::erase(iterator position)
{
node_pointer node = table::get_node(position);
BOOST_ASSERT(node);
node_pointer next = table::node_algo::next_node(node);
node_pointer next = table::next_node(node);
table_.erase_nodes_unique(node, next);
return iterator(next);
}
@ -1584,7 +1580,7 @@ unordered_map<K, T, H, P, A>::erase(const_iterator position)
{
node_pointer node = table::get_node(position);
BOOST_ASSERT(node);
node_pointer next = table::node_algo::next_node(node);
node_pointer next = table::next_node(node);
table_.erase_nodes_unique(node, next);
return iterator(next);
}
@ -1644,7 +1640,6 @@ void unordered_map<K, T, H, P, A>::merge(
}
#endif
#if BOOST_UNORDERED_INTEROPERABLE_NODES
template <class K, class T, class H, class P, class A>
template <typename H2, typename P2>
void unordered_map<K, T, H, P, A>::merge(
@ -1662,7 +1657,6 @@ void unordered_map<K, T, H, P, A>::merge(
table_.merge_unique(source.table_);
}
#endif
#endif
// observers
@ -1729,8 +1723,7 @@ std::pair<typename unordered_map<K, T, H, P, A>::iterator,
unordered_map<K, T, H, P, A>::equal_range(const key_type& k)
{
node_pointer n = table_.find_node(k);
return std::make_pair(
iterator(n), iterator(n ? table::node_algo::next_node(n) : n));
return std::make_pair(iterator(n), iterator(n ? table::next_node(n) : n));
}
template <class K, class T, class H, class P, class A>
@ -1739,8 +1732,8 @@ std::pair<typename unordered_map<K, T, H, P, A>::const_iterator,
unordered_map<K, T, H, P, A>::equal_range(const key_type& k) const
{
node_pointer n = table_.find_node(k);
return std::make_pair(const_iterator(n),
const_iterator(n ? table::node_algo::next_node(n) : n));
return std::make_pair(
const_iterator(n), const_iterator(n ? table::next_node(n) : n));
}
template <class K, class T, class H, class P, class A>
@ -2060,7 +2053,7 @@ unordered_multimap<K, T, H, P, A>::erase(iterator position)
{
node_pointer node = table::get_node(position);
BOOST_ASSERT(node);
node_pointer next = table::node_algo::next_node(node);
node_pointer next = table::next_node(node);
table_.erase_nodes_equiv(node, next);
return iterator(next);
}
@ -2071,7 +2064,7 @@ unordered_multimap<K, T, H, P, A>::erase(const_iterator position)
{
node_pointer node = table::get_node(position);
BOOST_ASSERT(node);
node_pointer next = table::node_algo::next_node(node);
node_pointer next = table::next_node(node);
table_.erase_nodes_equiv(node, next);
return iterator(next);
}
@ -2152,7 +2145,6 @@ void unordered_multimap<K, T, H, P, A>::merge(
}
#endif
#if BOOST_UNORDERED_INTEROPERABLE_NODES
template <class K, class T, class H, class P, class A>
template <typename H2, typename P2>
void unordered_multimap<K, T, H, P, A>::merge(
@ -2174,7 +2166,6 @@ void unordered_multimap<K, T, H, P, A>::merge(
}
}
#endif
#endif
// lookup
@ -2217,7 +2208,7 @@ typename unordered_multimap<K, T, H, P, A>::size_type
unordered_multimap<K, T, H, P, A>::count(const key_type& k) const
{
node_pointer n = table_.find_node(k);
return n ? table::node_algo::count(n, &table_) : 0;
return n ? table_.group_count(n) : 0;
}
template <class K, class T, class H, class P, class A>
@ -2226,8 +2217,7 @@ std::pair<typename unordered_multimap<K, T, H, P, A>::iterator,
unordered_multimap<K, T, H, P, A>::equal_range(const key_type& k)
{
node_pointer n = table_.find_node(k);
return std::make_pair(iterator(n),
iterator(n ? table::node_algo::next_group(n, &table_) : n));
return std::make_pair(iterator(n), iterator(n ? table_.next_group(n) : n));
}
template <class K, class T, class H, class P, class A>
@ -2236,8 +2226,8 @@ std::pair<typename unordered_multimap<K, T, H, P, A>::const_iterator,
unordered_multimap<K, T, H, P, A>::equal_range(const key_type& k) const
{
node_pointer n = table_.find_node(k);
return std::make_pair(const_iterator(n),
const_iterator(n ? table::node_algo::next_group(n, &table_) : n));
return std::make_pair(
const_iterator(n), const_iterator(n ? table_.next_group(n) : n));
}
template <class K, class T, class H, class P, class A>

View File

@ -144,7 +144,7 @@ template <class T, class H, class P, class A> class unordered_set
#if defined(BOOST_UNORDERED_USE_MOVE)
unordered_set& operator=(BOOST_COPY_ASSIGN_REF(unordered_set) x)
{
table_.assign(x.table_);
table_.assign(x.table_, true);
return *this;
}
@ -154,13 +154,13 @@ template <class T, class H, class P, class A> class unordered_set
// is_nothrow_move_assignable_v<H> &&
// is_nothrow_move_assignable_v<P>)
{
table_.move_assign(x.table_);
table_.move_assign(x.table_, true);
return *this;
}
#else
unordered_set& operator=(unordered_set const& x)
{
table_.assign(x.table_);
table_.assign(x.table_, true);
return *this;
}
@ -171,7 +171,7 @@ template <class T, class H, class P, class A> class unordered_set
// is_nothrow_move_assignable_v<H> &&
// is_nothrow_move_assignable_v<P>)
{
table_.move_assign(x.table_);
table_.move_assign(x.table_, true);
return *this;
}
#endif
@ -457,14 +457,12 @@ template <class T, class H, class P, class A> class unordered_set
void merge(boost::unordered_set<T, H2, P2, A>&& source);
#endif
#if BOOST_UNORDERED_INTEROPERABLE_NODES
template <typename H2, typename P2>
void merge(boost::unordered_multiset<T, H2, P2, A>& source);
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
template <typename H2, typename P2>
void merge(boost::unordered_multiset<T, H2, P2, A>&& source);
#endif
#endif
// observers
@ -562,7 +560,7 @@ template <class T, class H, class P, class A> class unordered_multiset
typedef A allocator_type;
private:
typedef boost::unordered::detail::multiset<A, T, H, P> types;
typedef boost::unordered::detail::set<A, T, H, P> types;
typedef typename types::value_allocator_traits value_allocator_traits;
typedef typename types::table table;
typedef typename table::node_pointer node_pointer;
@ -656,7 +654,7 @@ template <class T, class H, class P, class A> class unordered_multiset
#if defined(BOOST_UNORDERED_USE_MOVE)
unordered_multiset& operator=(BOOST_COPY_ASSIGN_REF(unordered_multiset) x)
{
table_.assign(x.table_);
table_.assign(x.table_, false);
return *this;
}
@ -666,13 +664,13 @@ template <class T, class H, class P, class A> class unordered_multiset
// is_nothrow_move_assignable_v<H> &&
// is_nothrow_move_assignable_v<P>)
{
table_.move_assign(x.table_);
table_.move_assign(x.table_, false);
return *this;
}
#else
unordered_multiset& operator=(unordered_multiset const& x)
{
table_.assign(x.table_);
table_.assign(x.table_, false);
return *this;
}
@ -683,7 +681,7 @@ template <class T, class H, class P, class A> class unordered_multiset
// is_nothrow_move_assignable_v<H> &&
// is_nothrow_move_assignable_v<P>)
{
table_.move_assign(x.table_);
table_.move_assign(x.table_, false);
return *this;
}
#endif
@ -962,14 +960,12 @@ template <class T, class H, class P, class A> class unordered_multiset
void merge(boost::unordered_multiset<T, H2, P2, A>&& source);
#endif
#if BOOST_UNORDERED_INTEROPERABLE_NODES
template <typename H2, typename P2>
void merge(boost::unordered_set<T, H2, P2, A>& source);
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
template <typename H2, typename P2>
void merge(boost::unordered_set<T, H2, P2, A>&& source);
#endif
#endif
// observers
@ -1246,7 +1242,7 @@ typename unordered_set<T, H, P, A>::iterator unordered_set<T, H, P, A>::erase(
{
node_pointer node = table::get_node(position);
BOOST_ASSERT(node);
node_pointer next = table::node_algo::next_node(node);
node_pointer next = table::next_node(node);
table_.erase_nodes_unique(node, next);
return iterator(next);
}
@ -1322,7 +1318,6 @@ void unordered_set<T, H, P, A>::merge(
}
#endif
#if BOOST_UNORDERED_INTEROPERABLE_NODES
template <class T, class H, class P, class A>
template <typename H2, typename P2>
void unordered_set<T, H, P, A>::merge(
@ -1340,7 +1335,6 @@ void unordered_set<T, H, P, A>::merge(
table_.merge_unique(source.table_);
}
#endif
#endif
// lookup
@ -1374,8 +1368,8 @@ std::pair<typename unordered_set<T, H, P, A>::const_iterator,
unordered_set<T, H, P, A>::equal_range(const key_type& k) const
{
node_pointer n = table_.find_node(k);
return std::make_pair(const_iterator(n),
const_iterator(n ? table::node_algo::next_node(n) : n));
return std::make_pair(
const_iterator(n), const_iterator(n ? table::next_node(n) : n));
}
template <class T, class H, class P, class A>
@ -1652,7 +1646,7 @@ unordered_multiset<T, H, P, A>::erase(const_iterator position)
{
node_pointer node = table::get_node(position);
BOOST_ASSERT(node);
node_pointer next = table::node_algo::next_node(node);
node_pointer next = table::next_node(node);
table_.erase_nodes_equiv(node, next);
return iterator(next);
}
@ -1732,7 +1726,6 @@ void unordered_multiset<T, H, P, A>::merge(
}
#endif
#if BOOST_UNORDERED_INTEROPERABLE_NODES
template <class T, class H, class P, class A>
template <typename H2, typename P2>
void unordered_multiset<T, H, P, A>::merge(
@ -1754,7 +1747,6 @@ void unordered_multiset<T, H, P, A>::merge(
}
}
#endif
#endif
// lookup
@ -1780,7 +1772,7 @@ typename unordered_multiset<T, H, P, A>::size_type
unordered_multiset<T, H, P, A>::count(const key_type& k) const
{
node_pointer n = table_.find_node(k);
return n ? table::node_algo::count(n, &table_) : 0;
return n ? table_.group_count(n) : 0;
}
template <class T, class H, class P, class A>
@ -1789,8 +1781,8 @@ std::pair<typename unordered_multiset<T, H, P, A>::const_iterator,
unordered_multiset<T, H, P, A>::equal_range(const key_type& k) const
{
node_pointer n = table_.find_node(k);
return std::make_pair(const_iterator(n),
const_iterator(n ? table::node_algo::next_group(n, &table_) : n));
return std::make_pair(
const_iterator(n), const_iterator(n ? table_.next_group(n) : n));
}
template <class T, class H, class P, class A>

View File

@ -63,20 +63,28 @@ template <class X> void check_equivalent_keys(X const& x1)
BOOST_DEDUCED_TYPENAME X::size_type bucket = x1.bucket(key);
BOOST_DEDUCED_TYPENAME X::const_local_iterator lit = x1.begin(bucket),
lend = x1.end(bucket);
for (; lit != lend && !eq(get_key<X>(*lit), key); ++lit)
continue;
if (lit == lend)
unsigned int count_checked = 0;
for (; lit != lend && !eq(get_key<X>(*lit), key); ++lit) {
++count_checked;
}
if (lit == lend) {
BOOST_ERROR("Unable to find element with a local_iterator");
unsigned int count2 = 0;
for (; lit != lend && eq(get_key<X>(*lit), key); ++lit)
++count2;
if (count != count2)
BOOST_ERROR("Element count doesn't match local_iterator.");
for (; lit != lend; ++lit) {
if (eq(get_key<X>(*lit), key)) {
BOOST_ERROR("Non-adjacent element with equivalent key "
"in bucket.");
break;
std::cerr << "Checked: " << count_checked << " elements"
<< std::endl;
} else {
unsigned int count2 = 0;
for (; lit != lend && eq(get_key<X>(*lit), key); ++lit)
++count2;
if (count != count2)
BOOST_ERROR("Element count doesn't match local_iterator.");
for (; lit != lend; ++lit) {
if (eq(get_key<X>(*lit), key)) {
BOOST_ERROR("Non-adjacent element with equivalent key "
"in bucket.");
break;
}
}
}
};

View File

@ -93,8 +93,6 @@ static inline void run_tests()
<< BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT << "\n" \
<< "BOOST_UNORDERED_EMPLACE_LIMIT: " \
<< BOOST_UNORDERED_EMPLACE_LIMIT << "\n" \
<< "BOOST_UNORDERED_INTEROPERABLE_NODES: " \
<< BOOST_UNORDERED_INTEROPERABLE_NODES << "\n" \
<< "BOOST_UNORDERED_USE_ALLOCATOR_TRAITS: " \
<< BOOST_UNORDERED_USE_ALLOCATOR_TRAITS << "\n" \
<< "BOOST_UNORDERED_CXX11_CONSTRUCTION: " \

View File

@ -47,9 +47,9 @@ std::size_t hash_value(insert_stable::member const& x)
}
}
// This is now only supported when using grouped nodes. I can't see any
// efficient way to do it otherwise.
#if !BOOST_UNORDERED_INTEROPERABLE_NODES
// This is no longer supported, as there's no longer an efficient way to get to
// the end of a group of equivalent nodes.
#if 0
UNORDERED_AUTO_TEST(stable_insert_test1)
{

View File

@ -99,7 +99,6 @@ UNORDERED_AUTO_TEST(merge_multiset)
test::check_equivalent_keys(y);
}
#if BOOST_UNORDERED_INTEROPERABLE_NODES
UNORDERED_AUTO_TEST(merge_set_and_multiset)
{
boost::unordered_set<int> x;
@ -139,7 +138,6 @@ UNORDERED_AUTO_TEST(merge_set_and_multiset)
test::check_equivalent_keys(x);
test::check_equivalent_keys(y);
}
#endif
template <class X> void merge_empty_test(X*, test::random_generator generator)
{