(Here is a link to this same code on Wandbox)
C++17 introduces member functions extract
and insert
on node-based containers (such as set
and map
) so that single elements can be transferred from container A to container B without any move operations. It also adds member function merge
to transfer all contents of container A into container B. But there's no "range merge
" function provided by the STL as far as I can tell. So, inspired by this question, I decided to write an iterator adaptor similar to std::move_iterator
but which can be used to extract and insert instead of moving. Here's my usage example:
// snipped the definitions of classes K and V, but see the Wandbox if you care
int main()
{
using KV = std::pair<K,V>;
std::map<K,V> m1({
KV{ "k1", "v1" },
KV{ "k2", "v2" },
KV{ "k3", "v3" },
});
std::map<K,V> m2;
puts("Moving elements from m1 to m2...");
#if 0
// the old boring way
for (auto it = m1.begin(); it != m1.end(); ) {
m2.insert( m1.extract(it++) );
}
#else
// the shiny new way
auto begin = my::extract_iterator(m1, m1.begin());
auto end = my::extract_iterator(m1, m1.end());
auto out = my::insert_iterator(m2);
std::copy(begin, end, out);
#endif
puts("...done!");
std::cout << "Contents of m1:" << std::endl;
for (auto&& [k, v]: m1) {
std::cout << k << ' ' << v << std::endl;
}
std::cout << "Contents of m2:" << std::endl;
for (auto&& [k, v]: m2) {
std::cout << k << ' ' << v << std::endl;
}
}
And here's the implementation:
#include <algorithm>
#include <cassert>
#include <iostream>
#include <map>
namespace my
{
template<typename Container>
class insert_iterator
{
Container *container;
public:
using iterator_category = std::output_iterator_tag;
using difference_type = void;
using value_type = void;
using pointer = void;
using reference = void;
insert_iterator(Container& c) : container(&c) {}
insert_iterator& operator*() { return *this; }
insert_iterator& operator++() { return *this; }
insert_iterator operator++(int) { return *this; }
template<class N>
insert_iterator& operator=(N&& nh)
{
container->insert(std::forward<N>(nh));
return *this;
}
};
template<typename Container, typename Iterator>
class extract_iterator
{
Container *container;
Iterator iterator;
bool secretly_advanced = false;
public:
using iterator_category = std::input_iterator_tag;
using difference_type = void;
using value_type = void;
using pointer = void;
using reference = void;
extract_iterator(Container& c, Iterator it) : container(&c), iterator(std::move(it)) {}
auto operator*() {
assert(!secretly_advanced);
secretly_advanced = true;
return container->extract(iterator++);
}
extract_iterator& operator++() {
if (!secretly_advanced) ++iterator;
secretly_advanced = false;
return *this;
}
extract_iterator operator++(int) {
auto copy = *this;
if (!secretly_advanced) ++iterator;
secretly_advanced = false;
return copy;
}
bool operator==(const extract_iterator& o) const {
return secretly_advanced == o.secretly_advanced && iterator == o.iterator;
}
bool operator!=(const extract_iterator& o) const { return !(*this == o); }
};
} // namespace my
Notice that I'm using the default rules for class template parameter type deduction.
Notice that my input iterator (extract_iterator
) sets its value_type
to void
. I'm not 100% sure that's kosher.
The secretly_advanced
stuff is unfortunately necessary because libstdc++ implements its std::copy
as something like
while (first != last) {
*d_first++ = *first;
++first;
}
instead of
while (first != last) {
*d_first++ = *first++;
}
and evaluating *first
in my case invalidates first.iterator
, so I figured the best thing to do was to advance it inside operator*
itself and just keep track of whether it'd already been secretly advanced or not.
If I missed out on a chance to use some exciting new C++17ism, please tell me!
K
andV
so that we can compile this? Your link doesn't include any code (unless it's hidden in a script somewhere, but it would still be better in the question, for permanence). \$\endgroup\$K
andV
are right before main. I think that definitions are not that necessary, since it is more or less obvious what they are doing. \$\endgroup\$using K = std::string
? and the same forV
? \$\endgroup\$K
andV
with instrumented constructors and assignment operators, in order to "prove" that no move-constructions or assignments take place during thestd::copy
. But yeah, @TobySpeight, my intention is definitely that you ought to be able to use these iterators with anyK
andV
of your choosing. \$\endgroup\$