diff --git a/doc/container.qbk b/doc/container.qbk
index 081dd489..4065a8d9 100644
--- a/doc/container.qbk
+++ b/doc/container.qbk
@@ -318,6 +318,15 @@ does not internally perform any value_type destruction, copy/move construction/a
corresponding to the insertion of new elements or deletion of stored elements, which can sometimes compensate in terms of
performance for the extra burden of doing more pointer manipulation and an additional allocation per element.
+[*Splicing]. Since every element lives in its own node, [classref boost::container::stable_vector stable_vector] also offers
+the three `splice` overloads of `std::list`: all the elements of another
+[classref boost::container::stable_vector stable_vector], one of them, or a range of them, are transferred before a given
+position by moving their node pointers from one index to the other, without copying, moving or destroying any element.
+References, pointers and iterators to the transferred elements remain valid and refer to the same elements, so that
+elements that are referred to by address can be passed from one container to another. The two containers must have
+equal allocators, and the cost is linear in the number of transferred elements plus the number of elements that follow
+the insertion point and the transferred range.
+
[*Exception safety]. (according to [@http://www.boost.org/community/exception_safety.html Abrahams' terminology])
As [classref boost::container::stable_vector stable_vector] does not internally copy/move elements around, some
operations provide stronger exception safety guarantees than in vector:
@@ -2883,6 +2892,9 @@ collect them containers and build [*Boost.Container], a library targeted to a wi
[section:release_notes_boost_1_93_00 Boost 1.93 Release]
+* [classref boost::container::stable_vector stable_vector] gains the three `splice` overloads of `std::list`,
+ which transfer elements between two stable_vectors with equal allocators without copying, moving or destroying them.
+
* Containers now accept allocators whose \c value_type is \c void; such allocators
are rebound to the container's element type via \c real_allocator.
diff --git a/include/boost/container/stable_vector.hpp b/include/boost/container/stable_vector.hpp
index 68332ba9..41f9a227 100644
--- a/include/boost/container/stable_vector.hpp
+++ b/include/boost/container/stable_vector.hpp
@@ -1756,6 +1756,111 @@ class stable_vector
return iterator(last.node_pointer());
}
+ //! Requires: p must be a valid iterator of *this, and x must be a
+ //! stable_vector other than *this whose allocator compares equal to the
+ //! one of *this.
+ //!
+ //! Effects: Transfers all the elements of x to *this, before p,
+ //! without copying, moving or destroying any of them; x is left empty.
+ //!
+ //! Throws: If the index of *this has to grow and its allocation
+ //! throws, in which case neither *this nor x change.
+ //!
+ //! Complexity: Linear to x.size() plus the number of elements of
+ //! *this from p to the end.
+ //!
+ //! Note: Iterators, pointers and references to the elements of x
+ //! remain valid, and refer to the same elements, which are now in *this.
+ void splice(const_iterator p, stable_vector &x)
+ { this->splice(p, x, x.cbegin(), x.cend()); }
+
+ //! Effects: Same as splice(p, x), x being an rvalue.
+ void splice(const_iterator p, BOOST_RV_REF(stable_vector) x)
+ { this->splice(p, static_cast(x)); }
+
+ //! Requires: p must be a valid iterator of *this, i a valid
+ //! dereferenceable iterator of x, and x a stable_vector other than *this
+ //! whose allocator compares equal to the one of *this.
+ //!
+ //! Effects: Transfers the element pointed by i from x to *this,
+ //! before p, without copying, moving or destroying it.
+ //!
+ //! Throws: If the index of *this has to grow and its allocation
+ //! throws, in which case neither *this nor x change.
+ //!
+ //! Complexity: Linear to the number of elements of *this from p to
+ //! the end plus the number of elements of x after i.
+ //!
+ //! Note: Iterators, pointers and references to the element remain
+ //! valid, and refer to the same element, which is now in *this.
+ void splice(const_iterator p, stable_vector &x, const_iterator i)
+ {
+ const_iterator n(i);
+ this->splice(p, x, i, ++n);
+ }
+
+ //! Effects: Same as splice(p, x, i), x being an rvalue.
+ void splice(const_iterator p, BOOST_RV_REF(stable_vector) x,
+ const_iterator i)
+ { this->splice(p, static_cast(x), i); }
+
+ //! Requires: p must be a valid iterator of *this, [first, last) a
+ //! valid range of x, and x a stable_vector other than *this whose
+ //! allocator compares equal to the one of *this.
+ //!
+ //! Effects: Transfers the elements in [first, last) from x to *this,
+ //! before p, without copying, moving or destroying any of them.
+ //!
+ //! Throws: If the index of *this has to grow and its allocation
+ //! throws, in which case neither *this nor x change.
+ //!
+ //! Complexity: Linear to the distance between first and last plus
+ //! the number of elements of *this from p to the end plus the number of
+ //! elements of x from last to the end.
+ //!
+ //! Note: Iterators, pointers and references to the transferred
+ //! elements remain valid, and refer to the same elements, which are now
+ //! in *this.
+ void splice(const_iterator p, stable_vector &x,
+ const_iterator first, const_iterator last)
+ {
+ BOOST_ASSERT(this != &x);
+ BOOST_ASSERT(this->priv_node_alloc() == x.priv_node_alloc());
+ BOOST_ASSERT(this->priv_in_range_or_end(p));
+ BOOST_ASSERT(first == last ||
+ (first < last && x.priv_in_range(first) &&
+ x.priv_in_range_or_end(last)));
+ BOOST_CONTAINER_STABLE_VECTOR_CHECK_INVARIANT;
+ const size_type num = static_cast(last - first);
+ if(!num){
+ return;
+ }
+ const size_type idx = static_cast(p - this->cbegin());
+ const index_iterator x_first
+ (x.index.begin() + static_cast(first - x.cbegin()));
+ const index_iterator x_last(x_first + static_cast(num));
+ //Make room in the index of *this for the nodes of x, which is the only
+ //step that can throw, and fix up the pointers of the nodes that moved
+ index_traits_type::initialize_end_node
+ (this->index, this->internal_data.end_node, num);
+ const node_base_ptr_ptr old_buffer = this->index.data();
+ this->index.insert
+ (this->index.begin() + static_cast(idx),
+ x_first, x_last);
+ const index_iterator index_beg = this->index.begin();
+ index_traits_type::fix_up_pointers_from
+ (this->index, this->index.data() != old_buffer ?
+ index_beg : index_beg + static_cast(idx));
+ //Now the nodes belong to *this: drop them from the index of x
+ const index_iterator e = x.index.erase(x_first, x_last);
+ index_traits_type::fix_up_pointers_from(x.index, e);
+ }
+
+ //! Effects: Same as splice(p, x, first, last), x being an rvalue.
+ void splice(const_iterator p, BOOST_RV_REF(stable_vector) x,
+ const_iterator first, const_iterator last)
+ { this->splice(p, static_cast(x), first, last); }
+
//! Effects: Swaps the contents of *this and x.
//!
//! Throws: Nothing.
diff --git a/test/stable_vector_test.cpp b/test/stable_vector_test.cpp
index 046bd615..d1598b20 100644
--- a/test/stable_vector_test.cpp
+++ b/test/stable_vector_test.cpp
@@ -122,8 +122,99 @@ bool test_unqualified_swap()
return true;
}
+//Checks that sv holds the values of v, in order, through iterators and operator[]
+bool test_splice_check(const stable_vector &sv,
+ const int *v, std::size_t n)
+{
+ if(sv.size() != n)
+ return false;
+ std::size_t i = 0;
+ for( stable_vector::const_iterator it = sv.begin()
+ ; it != sv.end(); ++it, ++i){
+ if(it->get_int() != v[i] || sv[i].get_int() != v[i])
+ return false;
+ }
+ return true;
+}
+
+bool test_splice()
+{
+ typedef stable_vector cont;
+ cont a, b, c;
+ for(int i = 0; i < 5; ++i)
+ a.emplace_back(i); //0 1 2 3 4
+ for(int i = 10; i < 13; ++i)
+ b.emplace_back(i); //10 11 12
+ const unsigned int count = test::non_copymovable_int::count;
+ const test::non_copymovable_int *p11 = &b[1], *p2 = &a[2], *p4 = &a[4];
+
+ //All of b, in the middle of a
+ a.splice(a.cbegin() + 2, b);
+ { const int v[] = { 0, 1, 10, 11, 12, 2, 3, 4 };
+ if(!test_splice_check(a, v, 8) || !b.empty())
+ return false;
+ }
+ if(&a[3] != p11 || &a[5] != p2 || &a[7] != p4)
+ return false;
+
+ //One element, into an empty stable_vector
+ c.splice(c.cend(), a, a.cbegin() + 3);
+ { const int v[] = { 11 }, w[] = { 0, 1, 10, 12, 2, 3, 4 };
+ if(!test_splice_check(c, v, 1) || !test_splice_check(a, w, 7) || &c[0] != p11)
+ return false;
+ }
+
+ //A range at the end of a, at the beginning of c
+ c.splice(c.cbegin(), a, a.cbegin() + 4, a.cend());
+ { const int v[] = { 2, 3, 4, 11 }, w[] = { 0, 1, 10, 12 };
+ if(!test_splice_check(c, v, 4) || !test_splice_check(a, w, 4))
+ return false;
+ }
+ if(&c[0] != p2 || &c[2] != p4)
+ return false;
+
+ //An empty range changes nothing
+ a.splice(a.cend(), c, c.cbegin(), c.cbegin());
+ { const int v[] = { 2, 3, 4, 11 }, w[] = { 0, 1, 10, 12 };
+ if(!test_splice_check(c, v, 4) || !test_splice_check(a, w, 4))
+ return false;
+ }
+
+ //Splicing constructs and destroys nothing
+ if(test::non_copymovable_int::count != count)
+ return false;
+
+ //A growth of the index of the receiving stable_vector, and back
+ for(int i = 0; i < 1000; ++i)
+ b.emplace_back(100 + i);
+ const test::non_copymovable_int *p100 = &b[0], *p1099 = &b[999];
+ a.splice(a.cbegin() + 1, b);
+ if(a.size() != 1004 || !b.empty() || &a[1] != p100 || &a[1000] != p1099)
+ return false;
+ b.splice(b.cend(), a, a.cbegin() + 1, a.cbegin() + 1001);
+ { const int w[] = { 0, 1, 10, 12 };
+ if(!test_splice_check(a, w, 4) || b.size() != 1000 || &b[0] != p100 || &b[999] != p1099)
+ return false;
+ }
+
+ //Both stable_vectors work as usual afterwards
+ a.erase(a.cbegin() + 2);
+ a.emplace(a.cbegin(), 7);
+ c.splice(c.cend(), boost::move(a));
+ { const int v[] = { 2, 3, 4, 11, 7, 0, 1, 12 };
+ if(!test_splice_check(c, v, 8) || !a.empty())
+ return false;
+ }
+ return true;
+}
+
int main()
{
+ if(!test_splice()){
+ std::cerr << "test_splice failed" << std::endl;
+ return 1;
+ }
+
if(!test_unqualified_swap())
return 1;