mirror of
https://github.com/boostorg/unordered.git
synced 2025-07-30 03:17:15 +02:00
Merge pull request #173 from cmazakas/fix/exception-guarantees
Exception Guarantees
This commit is contained in:
@ -98,9 +98,8 @@ namespace boost {
|
|||||||
xref:#unordered_flat_map_destructor[~unordered_flat_map]();
|
xref:#unordered_flat_map_destructor[~unordered_flat_map]();
|
||||||
unordered_flat_map& xref:#unordered_flat_map_copy_assignment[operator++=++](const unordered_flat_map& other);
|
unordered_flat_map& xref:#unordered_flat_map_copy_assignment[operator++=++](const unordered_flat_map& other);
|
||||||
unordered_flat_map& xref:#unordered_flat_map_move_assignment[operator++=++](unordered_flat_map&& other)
|
unordered_flat_map& xref:#unordered_flat_map_move_assignment[operator++=++](unordered_flat_map&& other)
|
||||||
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value &&
|
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value ||
|
||||||
boost::is_nothrow_move_assignable_v<Hash> &&
|
boost::allocator_traits<Allocator>::propagate_on_container_move_assignment::value);
|
||||||
boost::is_nothrow_move_assignable_v<Pred>);
|
|
||||||
unordered_flat_map& xref:#unordered_flat_map_initializer_list_assignment[operator++=++](std::initializer_list<value_type>);
|
unordered_flat_map& xref:#unordered_flat_map_initializer_list_assignment[operator++=++](std::initializer_list<value_type>);
|
||||||
allocator_type xref:#unordered_flat_map_get_allocator[get_allocator]() const noexcept;
|
allocator_type xref:#unordered_flat_map_get_allocator[get_allocator]() const noexcept;
|
||||||
|
|
||||||
@ -154,9 +153,8 @@ namespace boost {
|
|||||||
template<class K> size_type xref:#unordered_flat_map_transparent_erase_by_key[erase](K&& k);
|
template<class K> size_type xref:#unordered_flat_map_transparent_erase_by_key[erase](K&& k);
|
||||||
iterator xref:#unordered_flat_map_erase_range[erase](const_iterator first, const_iterator last);
|
iterator xref:#unordered_flat_map_erase_range[erase](const_iterator first, const_iterator last);
|
||||||
void xref:#unordered_flat_map_swap[swap](unordered_flat_map& other)
|
void xref:#unordered_flat_map_swap[swap](unordered_flat_map& other)
|
||||||
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value &&
|
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value ||
|
||||||
boost::is_nothrow_swappable_v<Hash> &&
|
boost::allocator_traits<Allocator>::propagate_on_container_swap::value);
|
||||||
boost::is_nothrow_swappable_v<Pred>);
|
|
||||||
void xref:#unordered_flat_map_clear[clear]() noexcept;
|
void xref:#unordered_flat_map_clear[clear]() noexcept;
|
||||||
|
|
||||||
template<class H2, class P2>
|
template<class H2, class P2>
|
||||||
@ -606,11 +604,10 @@ Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInse
|
|||||||
==== Move Assignment
|
==== Move Assignment
|
||||||
```c++
|
```c++
|
||||||
unordered_flat_map& operator=(unordered_flat_map&& other)
|
unordered_flat_map& operator=(unordered_flat_map&& other)
|
||||||
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value &&
|
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value ||
|
||||||
boost::is_nothrow_move_assignable_v<Hash> &&
|
boost::allocator_traits<Allocator>::propagate_on_container_move_assignment::value);
|
||||||
boost::is_nothrow_move_assignable_v<Pred>);
|
|
||||||
```
|
```
|
||||||
The move assignment operator. Destroys previously existing elements, move-assigns the hash function and predicate from `other`,
|
The move assignment operator. Destroys previously existing elements, swaps the hash function and predicate from `other`,
|
||||||
and move-assigns the allocator from `other` if `Alloc::propagate_on_container_move_assignment` exists and `Alloc::propagate_on_container_move_assignment::value` is `true`.
|
and move-assigns the allocator from `other` if `Alloc::propagate_on_container_move_assignment` exists and `Alloc::propagate_on_container_move_assignment::value` is `true`.
|
||||||
If at this point the allocator is equal to `other.get_allocator()`, the internal bucket array of `other` is transferred directly to the new container;
|
If at this point the allocator is equal to `other.get_allocator()`, the internal bucket array of `other` is transferred directly to the new container;
|
||||||
otherwise, inserts move-constructed copies of the elements of `other`.
|
otherwise, inserts move-constructed copies of the elements of `other`.
|
||||||
@ -1043,9 +1040,8 @@ Throws:;; Nothing in this implementation (neither the `hasher` nor the `key_equa
|
|||||||
==== swap
|
==== swap
|
||||||
```c++
|
```c++
|
||||||
void swap(unordered_flat_map& other)
|
void swap(unordered_flat_map& other)
|
||||||
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value &&
|
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value ||
|
||||||
boost::is_nothrow_swappable_v<Hash> &&
|
boost::allocator_traits<Allocator>::propagate_on_container_swap::value);
|
||||||
boost::is_nothrow_swappable_v<Pred>);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Swaps the contents of the container with the parameter.
|
Swaps the contents of the container with the parameter.
|
||||||
|
@ -93,9 +93,8 @@ namespace boost {
|
|||||||
xref:#unordered_flat_set_destructor[~unordered_flat_set]();
|
xref:#unordered_flat_set_destructor[~unordered_flat_set]();
|
||||||
unordered_flat_set& xref:#unordered_flat_set_copy_assignment[operator++=++](const unordered_flat_set& other);
|
unordered_flat_set& xref:#unordered_flat_set_copy_assignment[operator++=++](const unordered_flat_set& other);
|
||||||
unordered_flat_set& xref:#unordered_flat_set_move_assignment[operator++=++](unordered_flat_set&& other)
|
unordered_flat_set& xref:#unordered_flat_set_move_assignment[operator++=++](unordered_flat_set&& other)
|
||||||
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value &&
|
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value ||
|
||||||
boost::is_nothrow_move_assignable_v<Hash> &&
|
boost::allocator_traits<Allocator>::propagate_on_container_move_assignment::value);
|
||||||
boost::is_nothrow_move_assignable_v<Pred>);
|
|
||||||
unordered_flat_set& xref:#unordered_flat_set_initializer_list_assignment[operator++=++](std::initializer_list<value_type>);
|
unordered_flat_set& xref:#unordered_flat_set_initializer_list_assignment[operator++=++](std::initializer_list<value_type>);
|
||||||
allocator_type xref:#unordered_flat_set_get_allocator[get_allocator]() const noexcept;
|
allocator_type xref:#unordered_flat_set_get_allocator[get_allocator]() const noexcept;
|
||||||
|
|
||||||
@ -128,9 +127,8 @@ namespace boost {
|
|||||||
template<class K> size_type xref:#unordered_flat_set_transparent_erase_by_key[erase](K&& k);
|
template<class K> size_type xref:#unordered_flat_set_transparent_erase_by_key[erase](K&& k);
|
||||||
iterator xref:#unordered_flat_set_erase_range[erase](const_iterator first, const_iterator last);
|
iterator xref:#unordered_flat_set_erase_range[erase](const_iterator first, const_iterator last);
|
||||||
void xref:#unordered_flat_set_swap[swap](unordered_flat_set& other)
|
void xref:#unordered_flat_set_swap[swap](unordered_flat_set& other)
|
||||||
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value &&
|
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value ||
|
||||||
boost::is_nothrow_swappable_v<Hash> &&
|
boost::allocator_traits<Allocator>::propagate_on_container_swap::value);
|
||||||
boost::is_nothrow_swappable_v<Pred>);
|
|
||||||
void xref:#unordered_flat_set_clear[clear]() noexcept;
|
void xref:#unordered_flat_set_clear[clear]() noexcept;
|
||||||
|
|
||||||
template<class H2, class P2>
|
template<class H2, class P2>
|
||||||
@ -565,11 +563,10 @@ Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInse
|
|||||||
==== Move Assignment
|
==== Move Assignment
|
||||||
```c++
|
```c++
|
||||||
unordered_flat_set& operator=(unordered_flat_set&& other)
|
unordered_flat_set& operator=(unordered_flat_set&& other)
|
||||||
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value &&
|
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value ||
|
||||||
boost::is_nothrow_move_assignable_v<Hash> &&
|
boost::allocator_traits<Allocator>::propagate_on_container_move_assignment::value);
|
||||||
boost::is_nothrow_move_assignable_v<Pred>);
|
|
||||||
```
|
```
|
||||||
The move assignment operator. Destroys previously existing elements, move-assigns the hash function and predicate from `other`,
|
The move assignment operator. Destroys previously existing elements, swaps the hash function and predicate from `other`,
|
||||||
and move-assigns the allocator from `other` if `Alloc::propagate_on_container_move_assignment` exists and `Alloc::propagate_on_container_move_assignment::value` is `true`.
|
and move-assigns the allocator from `other` if `Alloc::propagate_on_container_move_assignment` exists and `Alloc::propagate_on_container_move_assignment::value` is `true`.
|
||||||
If at this point the allocator is equal to `other.get_allocator()`, the internal bucket array of `other` is transferred directly to the new container;
|
If at this point the allocator is equal to `other.get_allocator()`, the internal bucket array of `other` is transferred directly to the new container;
|
||||||
otherwise, inserts move-constructed copies of the elements of `other`.
|
otherwise, inserts move-constructed copies of the elements of `other`.
|
||||||
@ -863,9 +860,8 @@ Throws:;; Nothing in this implementation (neither the `hasher` nor the `key_equa
|
|||||||
==== swap
|
==== swap
|
||||||
```c++
|
```c++
|
||||||
void swap(unordered_flat_set& other)
|
void swap(unordered_flat_set& other)
|
||||||
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value &&
|
noexcept(boost::allocator_traits<Allocator>::is_always_equal::value ||
|
||||||
boost::is_nothrow_swappable_v<Hash> &&
|
boost::allocator_traits<Allocator>::propagate_on_container_swap::value);
|
||||||
boost::is_nothrow_swappable_v<Pred>);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Swaps the contents of the container with the parameter.
|
Swaps the contents of the container with the parameter.
|
||||||
|
@ -69,6 +69,12 @@
|
|||||||
}while(0)
|
}while(0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred) \
|
||||||
|
static_assert(boost::is_nothrow_swappable<Hash>::value, \
|
||||||
|
"Template parameter Hash is required to be nothrow Swappable."); \
|
||||||
|
static_assert(boost::is_nothrow_swappable<Pred>::value, \
|
||||||
|
"Template parameter Pred is required to be nothrow Swappable");
|
||||||
|
|
||||||
namespace boost{
|
namespace boost{
|
||||||
namespace unordered{
|
namespace unordered{
|
||||||
namespace detail{
|
namespace detail{
|
||||||
@ -1260,13 +1266,28 @@ public:
|
|||||||
|
|
||||||
table& operator=(const table& x)
|
table& operator=(const table& x)
|
||||||
{
|
{
|
||||||
|
BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred)
|
||||||
|
|
||||||
static constexpr auto pocca=
|
static constexpr auto pocca=
|
||||||
alloc_traits::propagate_on_container_copy_assignment::value;
|
alloc_traits::propagate_on_container_copy_assignment::value;
|
||||||
|
|
||||||
if(this!=std::addressof(x)){
|
if(this!=std::addressof(x)){
|
||||||
clear();
|
// if copy construction here winds up throwing, the container is still
|
||||||
h()=x.h();
|
// left intact so we perform these operations first
|
||||||
pred()=x.pred();
|
hasher tmp_h=x.h();
|
||||||
|
key_equal tmp_p=x.pred();
|
||||||
|
|
||||||
|
// already noexcept, clear() before we swap the Hash, Pred just in case
|
||||||
|
// the clear() impl relies on them at some point in the future
|
||||||
|
clear();
|
||||||
|
|
||||||
|
// because we've asserted at compile-time that Hash and Pred are nothrow
|
||||||
|
// swappable, we can safely mutate our source container and maintain
|
||||||
|
// consistency between the Hash, Pred compatibility
|
||||||
|
using std::swap;
|
||||||
|
swap(h(),tmp_h);
|
||||||
|
swap(pred(),tmp_p);
|
||||||
|
|
||||||
if_constexpr<pocca>([&,this]{
|
if_constexpr<pocca>([&,this]{
|
||||||
if(al()!=x.al())reserve(0);
|
if(al()!=x.al())reserve(0);
|
||||||
copy_assign_if<pocca>(al(),x.al());
|
copy_assign_if<pocca>(al(),x.al());
|
||||||
@ -1285,19 +1306,32 @@ public:
|
|||||||
|
|
||||||
table& operator=(table&& x)
|
table& operator=(table&& x)
|
||||||
noexcept(
|
noexcept(
|
||||||
alloc_traits::is_always_equal::value&&
|
alloc_traits::propagate_on_container_move_assignment::value||
|
||||||
std::is_nothrow_move_assignable<Hash>::value&&
|
alloc_traits::is_always_equal::value)
|
||||||
std::is_nothrow_move_assignable<Pred>::value)
|
|
||||||
{
|
{
|
||||||
|
BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred)
|
||||||
|
|
||||||
static constexpr auto pocma=
|
static constexpr auto pocma=
|
||||||
alloc_traits::propagate_on_container_move_assignment::value;
|
alloc_traits::propagate_on_container_move_assignment::value;
|
||||||
|
|
||||||
if(this!=std::addressof(x)){
|
if(this!=std::addressof(x)){
|
||||||
|
/* Given ambiguity in implementation strategies briefly discussed here:
|
||||||
|
* https://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2227
|
||||||
|
*
|
||||||
|
* we opt into requiring nothrow swappability and eschew the move
|
||||||
|
* operations associated with Hash, Pred.
|
||||||
|
*
|
||||||
|
* To this end, we ensure that the user never has to consider the
|
||||||
|
* moved-from state of their Hash, Pred objects
|
||||||
|
*/
|
||||||
|
|
||||||
|
using std::swap;
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
h()=std::move(x.h());
|
swap(h(),x.h());
|
||||||
pred()=std::move(x.pred());
|
swap(pred(),x.pred());
|
||||||
|
|
||||||
if(pocma||al()==x.al()){
|
if(pocma||al()==x.al()){
|
||||||
using std::swap;
|
|
||||||
reserve(0);
|
reserve(0);
|
||||||
move_assign_if<pocma>(al(),x.al());
|
move_assign_if<pocma>(al(),x.al());
|
||||||
swap(size_,x.size_);
|
swap(size_,x.size_);
|
||||||
@ -1412,16 +1446,15 @@ public:
|
|||||||
|
|
||||||
void swap(table& x)
|
void swap(table& x)
|
||||||
noexcept(
|
noexcept(
|
||||||
alloc_traits::is_always_equal::value&&
|
alloc_traits::propagate_on_container_swap::value||
|
||||||
boost::is_nothrow_swappable<Hash>::value&&
|
alloc_traits::is_always_equal::value)
|
||||||
boost::is_nothrow_swappable<Pred>::value)
|
|
||||||
{
|
{
|
||||||
|
BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred)
|
||||||
|
|
||||||
static constexpr auto pocs=
|
static constexpr auto pocs=
|
||||||
alloc_traits::propagate_on_container_swap::value;
|
alloc_traits::propagate_on_container_swap::value;
|
||||||
|
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(h(),x.h());
|
|
||||||
swap(pred(),x.pred());
|
|
||||||
if_constexpr<pocs>([&,this]{
|
if_constexpr<pocs>([&,this]{
|
||||||
swap_if<pocs>(al(),x.al());
|
swap_if<pocs>(al(),x.al());
|
||||||
},
|
},
|
||||||
@ -1429,6 +1462,9 @@ public:
|
|||||||
BOOST_ASSERT(al()==x.al());
|
BOOST_ASSERT(al()==x.al());
|
||||||
(void)this; /* makes sure captured this is used */
|
(void)this; /* makes sure captured this is used */
|
||||||
});
|
});
|
||||||
|
|
||||||
|
swap(h(),x.h());
|
||||||
|
swap(pred(),x.pred());
|
||||||
swap(size_,x.size_);
|
swap(size_,x.size_);
|
||||||
swap(arrays,x.arrays);
|
swap(arrays,x.arrays);
|
||||||
swap(ml,x.ml);
|
swap(ml,x.ml);
|
||||||
@ -2075,6 +2111,7 @@ private:
|
|||||||
|
|
||||||
#undef BOOST_UNORDERED_ASSUME
|
#undef BOOST_UNORDERED_ASSUME
|
||||||
#undef BOOST_UNORDERED_HAS_BUILTIN
|
#undef BOOST_UNORDERED_HAS_BUILTIN
|
||||||
|
#undef BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED
|
||||||
#ifdef BOOST_UNORDERED_LITTLE_ENDIAN_NEON
|
#ifdef BOOST_UNORDERED_LITTLE_ENDIAN_NEON
|
||||||
#undef BOOST_UNORDERED_LITTLE_ENDIAN_NEON
|
#undef BOOST_UNORDERED_LITTLE_ENDIAN_NEON
|
||||||
#endif
|
#endif
|
||||||
|
@ -60,6 +60,9 @@ template <class T> struct assign_base : public test::exception_base
|
|||||||
test::random_values<T> x_values, y_values;
|
test::random_values<T> x_values, y_values;
|
||||||
T x, y;
|
T x, y;
|
||||||
|
|
||||||
|
int t1;
|
||||||
|
int t2;
|
||||||
|
|
||||||
typedef typename T::hasher hasher;
|
typedef typename T::hasher hasher;
|
||||||
typedef typename T::key_equal key_equal;
|
typedef typename T::key_equal key_equal;
|
||||||
typedef typename T::allocator_type allocator_type;
|
typedef typename T::allocator_type allocator_type;
|
||||||
@ -67,7 +70,10 @@ template <class T> struct assign_base : public test::exception_base
|
|||||||
assign_base(int tag1, int tag2, float mlf1 = 1.0, float mlf2 = 1.0)
|
assign_base(int tag1, int tag2, float mlf1 = 1.0, float mlf2 = 1.0)
|
||||||
: x_values(), y_values(),
|
: x_values(), y_values(),
|
||||||
x(0, hasher(tag1), key_equal(tag1), allocator_type(tag1)),
|
x(0, hasher(tag1), key_equal(tag1), allocator_type(tag1)),
|
||||||
y(0, hasher(tag2), key_equal(tag2), allocator_type(tag2))
|
y(0, hasher(tag2), key_equal(tag2), allocator_type(tag2)),
|
||||||
|
t1(tag1),
|
||||||
|
t2(tag2)
|
||||||
|
|
||||||
{
|
{
|
||||||
x.max_load_factor(mlf1);
|
x.max_load_factor(mlf1);
|
||||||
y.max_load_factor(mlf2);
|
y.max_load_factor(mlf2);
|
||||||
@ -89,6 +95,22 @@ template <class T> struct assign_base : public test::exception_base
|
|||||||
{
|
{
|
||||||
test::check_equivalent_keys(x1);
|
test::check_equivalent_keys(x1);
|
||||||
|
|
||||||
|
if (x1.hash_function() == hasher(t1)) {
|
||||||
|
BOOST_TEST(x1.key_eq() == key_equal(t1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x1.hash_function() == hasher(t2)) {
|
||||||
|
BOOST_TEST(x1.key_eq() == key_equal(t2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x1.key_eq() == key_equal(t1)) {
|
||||||
|
BOOST_TEST(x1.hash_function() == hasher(t1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x1.key_eq() == key_equal(t2)) {
|
||||||
|
BOOST_TEST(x1.hash_function() == hasher(t2));
|
||||||
|
}
|
||||||
|
|
||||||
// If the container is empty at the point of the exception, the
|
// If the container is empty at the point of the exception, the
|
||||||
// internal structure is hidden, this exposes it, at the cost of
|
// internal structure is hidden, this exposes it, at the cost of
|
||||||
// messing up the data.
|
// messing up the data.
|
||||||
|
@ -20,6 +20,7 @@ template <class T> struct move_assign_base : public test::exception_base
|
|||||||
{
|
{
|
||||||
test::random_values<T> x_values, y_values;
|
test::random_values<T> x_values, y_values;
|
||||||
T x, y;
|
T x, y;
|
||||||
|
int t1, t2;
|
||||||
|
|
||||||
typedef typename T::hasher hasher;
|
typedef typename T::hasher hasher;
|
||||||
typedef typename T::key_equal key_equal;
|
typedef typename T::key_equal key_equal;
|
||||||
@ -28,7 +29,9 @@ template <class T> struct move_assign_base : public test::exception_base
|
|||||||
move_assign_base(int tag1, int tag2, float mlf1 = 1.0, float mlf2 = 1.0)
|
move_assign_base(int tag1, int tag2, float mlf1 = 1.0, float mlf2 = 1.0)
|
||||||
: x_values(), y_values(),
|
: x_values(), y_values(),
|
||||||
x(0, hasher(tag1), key_equal(tag1), allocator_type(tag1)),
|
x(0, hasher(tag1), key_equal(tag1), allocator_type(tag1)),
|
||||||
y(0, hasher(tag2), key_equal(tag2), allocator_type(tag2))
|
y(0, hasher(tag2), key_equal(tag2), allocator_type(tag2)),
|
||||||
|
t1(tag1),
|
||||||
|
t2(tag2)
|
||||||
{
|
{
|
||||||
x.max_load_factor(mlf1);
|
x.max_load_factor(mlf1);
|
||||||
y.max_load_factor(mlf2);
|
y.max_load_factor(mlf2);
|
||||||
@ -52,6 +55,22 @@ template <class T> struct move_assign_base : public test::exception_base
|
|||||||
{
|
{
|
||||||
test::check_equivalent_keys(x1);
|
test::check_equivalent_keys(x1);
|
||||||
|
|
||||||
|
if (x1.hash_function() == hasher(t1)) {
|
||||||
|
BOOST_TEST(x1.key_eq() == key_equal(t1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x1.hash_function() == hasher(t2)) {
|
||||||
|
BOOST_TEST(x1.key_eq() == key_equal(t2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x1.key_eq() == key_equal(t1)) {
|
||||||
|
BOOST_TEST(x1.hash_function() == hasher(t1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x1.key_eq() == key_equal(t2)) {
|
||||||
|
BOOST_TEST(x1.hash_function() == hasher(t2));
|
||||||
|
}
|
||||||
|
|
||||||
// If the container is empty at the point of the exception, the
|
// If the container is empty at the point of the exception, the
|
||||||
// internal structure is hidden, this exposes it, at the cost of
|
// internal structure is hidden, this exposes it, at the cost of
|
||||||
// messing up the data.
|
// messing up the data.
|
||||||
|
@ -4,15 +4,40 @@
|
|||||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
|
||||||
#include "./containers.hpp"
|
#define BOOST_ENABLE_ASSERT_HANDLER
|
||||||
|
#include <boost/assert.hpp>
|
||||||
|
|
||||||
#if defined(BOOST_UNORDERED_FOA_TESTS)
|
#include "./containers.hpp"
|
||||||
#define BOOST_UNORDERED_FOA_WEAK_GUARANTEE_SWAP_EXCEPTIONS_TESTS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "../helpers/invariants.hpp"
|
#include "../helpers/invariants.hpp"
|
||||||
#include "../helpers/random_values.hpp"
|
#include "../helpers/random_values.hpp"
|
||||||
#include "../helpers/tracker.hpp"
|
#include "../helpers/tracker.hpp"
|
||||||
|
#include "../objects/test.hpp"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
void assertion_failed(
|
||||||
|
char const* expr, char const* function, char const* file, long line)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << expr << "\nin " << function << " failed at : " << file << ", line "
|
||||||
|
<< line;
|
||||||
|
|
||||||
|
throw std::runtime_error(ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertion_failed_msg(char const* expr, char const* msg,
|
||||||
|
char const* function, char const* file, long line)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << expr << "\nin " << function << " failed at : " << file << ", line "
|
||||||
|
<< line << "\n"
|
||||||
|
<< msg;
|
||||||
|
|
||||||
|
throw std::runtime_error(ss.str());
|
||||||
|
}
|
||||||
|
} // namespace boost
|
||||||
|
|
||||||
#if defined(BOOST_MSVC)
|
#if defined(BOOST_MSVC)
|
||||||
#pragma warning(disable : 4512) // assignment operator could not be generated
|
#pragma warning(disable : 4512) // assignment operator could not be generated
|
||||||
@ -39,15 +64,10 @@ template <class T> struct self_swap_base : public test::exception_base
|
|||||||
|
|
||||||
void check BOOST_PREVENT_MACRO_SUBSTITUTION(T const& x) const
|
void check BOOST_PREVENT_MACRO_SUBSTITUTION(T const& x) const
|
||||||
{
|
{
|
||||||
std::string scope(test::scope);
|
(void)x;
|
||||||
|
|
||||||
// TODO: In C++11 exceptions are only allowed in the swap function.
|
BOOST_ERROR("An exception leaked when it should not have. Allocator "
|
||||||
BOOST_TEST(scope == "hash::hash(hash)" ||
|
"equality assertion must precede all other ops");
|
||||||
scope == "hash::operator=(hash)" ||
|
|
||||||
scope == "equal_to::equal_to(equal_to)" ||
|
|
||||||
scope == "equal_to::operator=(equal_to)");
|
|
||||||
|
|
||||||
test::check_equivalent_keys(x);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -140,11 +160,133 @@ template <class T> struct swap_test4 : swap_base<T>
|
|||||||
swap_test4() : swap_base<T>(10, 10, 1, 2) {}
|
swap_test4() : swap_base<T>(10, 10, 1, 2) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <class T> struct unequal_alloc_swap_base : public test::exception_base
|
||||||
|
{
|
||||||
|
const test::random_values<T> x_values, y_values;
|
||||||
|
const T initial_x, initial_y;
|
||||||
|
|
||||||
|
typedef typename T::hasher hasher;
|
||||||
|
typedef typename T::key_equal key_equal;
|
||||||
|
typedef typename T::allocator_type allocator_type;
|
||||||
|
|
||||||
|
unequal_alloc_swap_base(unsigned int count1, unsigned int count2)
|
||||||
|
: x_values(count1, test::limited_range),
|
||||||
|
y_values(count2, test::limited_range),
|
||||||
|
initial_x(x_values.begin(), x_values.end(), 0, allocator_type(1337)),
|
||||||
|
initial_y(y_values.begin(), y_values.end(), 0, allocator_type(7331))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
struct data_type
|
||||||
|
{
|
||||||
|
data_type(T const& x_, T const& y_) : x(x_), y(y_) {}
|
||||||
|
|
||||||
|
T x, y;
|
||||||
|
};
|
||||||
|
|
||||||
|
data_type init() const { return data_type(initial_x, initial_y); }
|
||||||
|
|
||||||
|
void run(data_type& d) const
|
||||||
|
{
|
||||||
|
bool assert_threw = false;
|
||||||
|
|
||||||
|
BOOST_TEST(d.x.get_allocator() != d.y.get_allocator());
|
||||||
|
|
||||||
|
try {
|
||||||
|
d.x.swap(d.y);
|
||||||
|
} catch (std::runtime_error&) {
|
||||||
|
assert_threw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DISABLE_EXCEPTIONS;
|
||||||
|
BOOST_TEST(assert_threw);
|
||||||
|
test::check_container(d.x, this->x_values);
|
||||||
|
test::check_equivalent_keys(d.x);
|
||||||
|
test::check_container(d.y, this->y_values);
|
||||||
|
test::check_equivalent_keys(d.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void check BOOST_PREVENT_MACRO_SUBSTITUTION(data_type const& d) const
|
||||||
|
{
|
||||||
|
std::string scope(test::scope);
|
||||||
|
|
||||||
|
// TODO: In C++11 exceptions are only allowed in the swap function.
|
||||||
|
BOOST_TEST(scope == "hash::hash(hash)" ||
|
||||||
|
scope == "hash::operator=(hash)" ||
|
||||||
|
scope == "equal_to::equal_to(equal_to)" ||
|
||||||
|
scope == "equal_to::operator=(equal_to)");
|
||||||
|
|
||||||
|
test::check_equivalent_keys(d.x);
|
||||||
|
test::check_equivalent_keys(d.y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> struct unequal_alloc_swap_test1 : unequal_alloc_swap_base<T>
|
||||||
|
{
|
||||||
|
unequal_alloc_swap_test1() : unequal_alloc_swap_base<T>(0, 0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> struct unequal_alloc_swap_test2 : unequal_alloc_swap_base<T>
|
||||||
|
{
|
||||||
|
unequal_alloc_swap_test2() : unequal_alloc_swap_base<T>(0, 10) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> struct unequal_alloc_swap_test3 : unequal_alloc_swap_base<T>
|
||||||
|
{
|
||||||
|
unequal_alloc_swap_test3() : unequal_alloc_swap_base<T>(10, 0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> struct unequal_alloc_swap_test4 : unequal_alloc_swap_base<T>
|
||||||
|
{
|
||||||
|
unequal_alloc_swap_test4() : unequal_alloc_swap_base<T>(10, 10) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(BOOST_UNORDERED_FOA_TESTS)
|
||||||
|
|
||||||
|
using unordered_flat_set = boost::unordered_flat_set<int, boost::hash<int>,
|
||||||
|
std::equal_to<int>, test::allocator1<int> >;
|
||||||
|
using unordered_flat_map = boost::unordered_flat_map<int, int, boost::hash<int>,
|
||||||
|
std::equal_to<int>, test::allocator1<std::pair<int const, int> > >;
|
||||||
|
|
||||||
|
#define SWAP_CONTAINER_SEQ (unordered_flat_set)(unordered_flat_map)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
typedef boost::unordered_set<int, boost::hash<int>, std::equal_to<int>,
|
||||||
|
test::allocator1<int> >
|
||||||
|
unordered_set;
|
||||||
|
typedef boost::unordered_map<int, int, boost::hash<int>, std::equal_to<int>,
|
||||||
|
test::allocator1<std::pair<int const, int> > >
|
||||||
|
unordered_map;
|
||||||
|
typedef boost::unordered_multiset<int, boost::hash<int>, std::equal_to<int>,
|
||||||
|
test::allocator1<int> >
|
||||||
|
unordered_multiset;
|
||||||
|
typedef boost::unordered_multimap<int, int, boost::hash<int>,
|
||||||
|
std::equal_to<int>, test::allocator1<std::pair<int const, int> > >
|
||||||
|
unordered_multimap;
|
||||||
|
|
||||||
|
#define SWAP_CONTAINER_SEQ \
|
||||||
|
(unordered_set)(unordered_map)(unordered_multiset)(unordered_multimap)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// FOA containers deliberately choose to not offer the strong exception
|
||||||
|
// guarantee so we can't reliably test what happens if swapping one of the data
|
||||||
|
// members throws
|
||||||
|
//
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
#if !defined(BOOST_UNORDERED_FOA_TESTS)
|
||||||
EXCEPTION_TESTS(
|
EXCEPTION_TESTS(
|
||||||
(self_swap_test1)(self_swap_test2)
|
(self_swap_test1)(self_swap_test2)
|
||||||
(swap_test1)(swap_test2)(swap_test3)(swap_test4),
|
(swap_test1)(swap_test2)(swap_test3)(swap_test4),
|
||||||
CONTAINER_SEQ)
|
CONTAINER_SEQ)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// want to prove that when assertions are defined as throwing operations that we
|
||||||
|
// uphold invariants
|
||||||
|
EXCEPTION_TESTS(
|
||||||
|
(unequal_alloc_swap_test1)(unequal_alloc_swap_test2)
|
||||||
|
(unequal_alloc_swap_test3)(unequal_alloc_swap_test4),
|
||||||
|
SWAP_CONTAINER_SEQ)
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
RUN_TESTS()
|
RUN_TESTS()
|
||||||
|
@ -54,20 +54,10 @@ namespace test {
|
|||||||
if (test::has_unique_keys<X>::value && count != 1)
|
if (test::has_unique_keys<X>::value && count != 1)
|
||||||
BOOST_ERROR("Non-unique key.");
|
BOOST_ERROR("Non-unique key.");
|
||||||
|
|
||||||
#if !defined(BOOST_UNORDERED_FOA_WEAK_GUARANTEE_SWAP_EXCEPTIONS_TESTS)
|
|
||||||
// we conditionally compile this check because our FOA implementation only
|
|
||||||
// exhibits the weak guarantee when swapping throws
|
|
||||||
//
|
|
||||||
// in this case, the hasher may be changed before the predicate and the
|
|
||||||
// arrays are swapped in which case, we can can find an element by
|
|
||||||
// iteration but unfortunately, it's in the wrong slot according to the
|
|
||||||
// new hash function so count(key) can wind up returning nothing when
|
|
||||||
// there really is something
|
|
||||||
if (x1.count(key) != count) {
|
if (x1.count(key) != count) {
|
||||||
BOOST_ERROR("Incorrect output of count.");
|
BOOST_ERROR("Incorrect output of count.");
|
||||||
std::cerr << x1.count(key) << "," << count << "\n";
|
std::cerr << x1.count(key) << "," << count << "\n";
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef BOOST_UNORDERED_FOA_TESTS
|
#ifndef BOOST_UNORDERED_FOA_TESTS
|
||||||
// Check that the keys are in the correct bucket and are
|
// Check that the keys are in the correct bucket and are
|
||||||
|
@ -227,8 +227,21 @@ namespace test {
|
|||||||
}
|
}
|
||||||
return x1.tag_ != x2.tag_;
|
return x1.tag_ != x2.tag_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(BOOST_UNORDERED_FOA_TESTS)
|
||||||
|
friend void swap(hash&, hash&) noexcept;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if defined(BOOST_UNORDERED_FOA_TESTS)
|
||||||
|
void swap(hash& lhs, hash& rhs) noexcept
|
||||||
|
{
|
||||||
|
int tag = lhs.tag_;
|
||||||
|
lhs.tag_ = rhs.tag_;
|
||||||
|
rhs.tag_ = tag;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
class less
|
class less
|
||||||
{
|
{
|
||||||
int tag_;
|
int tag_;
|
||||||
@ -364,8 +377,20 @@ namespace test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
friend less create_compare(equal_to x) { return less(x.tag_); }
|
friend less create_compare(equal_to x) { return less(x.tag_); }
|
||||||
|
#if defined(BOOST_UNORDERED_FOA_TESTS)
|
||||||
|
friend void swap(equal_to&, equal_to&) noexcept;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if defined(BOOST_UNORDERED_FOA_TESTS)
|
||||||
|
void swap(equal_to& lhs, equal_to& rhs) noexcept
|
||||||
|
{
|
||||||
|
int tag = lhs.tag_;
|
||||||
|
lhs.tag_ = rhs.tag_;
|
||||||
|
rhs.tag_ = tag;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <class T> class allocator
|
template <class T> class allocator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -206,6 +206,11 @@ namespace test {
|
|||||||
hash& operator=(hash const&) { return *this; }
|
hash& operator=(hash const&) { return *this; }
|
||||||
~hash() {}
|
~hash() {}
|
||||||
|
|
||||||
|
#if defined(BOOST_UNORDERED_FOA_TESTS)
|
||||||
|
hash(hash&&) = default;
|
||||||
|
hash& operator=(hash&&) = default;
|
||||||
|
#endif
|
||||||
|
|
||||||
std::size_t operator()(T const&) const { return 0; }
|
std::size_t operator()(T const&) const { return 0; }
|
||||||
#if BOOST_UNORDERED_CHECK_ADDR_OPERATOR_NOT_USED
|
#if BOOST_UNORDERED_CHECK_ADDR_OPERATOR_NOT_USED
|
||||||
ampersand_operator_used operator&() const
|
ampersand_operator_used operator&() const
|
||||||
@ -224,6 +229,11 @@ namespace test {
|
|||||||
equal_to& operator=(equal_to const&) { return *this; }
|
equal_to& operator=(equal_to const&) { return *this; }
|
||||||
~equal_to() {}
|
~equal_to() {}
|
||||||
|
|
||||||
|
#if defined(BOOST_UNORDERED_FOA_TESTS)
|
||||||
|
equal_to(equal_to&&) = default;
|
||||||
|
equal_to& operator=(equal_to&&) = default;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool operator()(T const&, T const&) const { return true; }
|
bool operator()(T const&, T const&) const { return true; }
|
||||||
#if BOOST_UNORDERED_CHECK_ADDR_OPERATOR_NOT_USED
|
#if BOOST_UNORDERED_CHECK_ADDR_OPERATOR_NOT_USED
|
||||||
ampersand_operator_used operator&() const
|
ampersand_operator_used operator&() const
|
||||||
|
@ -437,13 +437,11 @@ UNORDERED_AUTO_TEST (prelim_allocator_checks) {
|
|||||||
using test::default_generator;
|
using test::default_generator;
|
||||||
|
|
||||||
#ifdef BOOST_UNORDERED_FOA_TESTS
|
#ifdef BOOST_UNORDERED_FOA_TESTS
|
||||||
boost::unordered_flat_set<int, noexcept_tests::hash_nothrow_move_assign,
|
boost::unordered_flat_set<int, noexcept_tests::hash_nothrow_swap,
|
||||||
noexcept_tests::equal_to_nothrow_move_assign, allocator1<int> >*
|
noexcept_tests::equal_to_nothrow_swap, allocator1<int> >* throwing_set_alloc1;
|
||||||
throwing_set_alloc1;
|
|
||||||
|
|
||||||
boost::unordered_flat_set<int, noexcept_tests::hash_nothrow_move_assign,
|
boost::unordered_flat_set<int, noexcept_tests::hash_nothrow_swap,
|
||||||
noexcept_tests::equal_to_nothrow_move_assign, allocator2<int> >*
|
noexcept_tests::equal_to_nothrow_swap, allocator2<int> >* throwing_set_alloc2;
|
||||||
throwing_set_alloc2;
|
|
||||||
|
|
||||||
UNORDERED_TEST(test_nothrow_move_assign_when_noexcept,
|
UNORDERED_TEST(test_nothrow_move_assign_when_noexcept,
|
||||||
((throwing_set_alloc1)(throwing_set_alloc2))((default_generator)))
|
((throwing_set_alloc1)(throwing_set_alloc2))((default_generator)))
|
||||||
|
Reference in New Issue
Block a user