Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Move objects from a set to another set with a different comparison functor

I have a large number of large objects in my program. They are currently stored in an std::set with a customer comparison functor. The set starts empty and I keep emplacing objects into it. I also periodically "consume" objects from one end of the set.

At a few (2-10) key points during the execution of my program, I might want to change the sorting of these objects and keep emplacing and consuming them in the new order. I usually have three possible orderings and I keep switching between them.

I would like the resorting to take place without copying the large objects, but rather moving them.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Consider the following example:

#include <iostream>
#include <set>
#include <utility>

struct S {
    int a;
    
    S(int a) : a{a} { std::cout << "Constructor\n"; }
    S(const S& s) : a{s.a} { std::cout << "Copy constructor\n"; }
    S(S&& s) : a{std::exchange(s.a, 0)} { std::cout << "Move constructor\n"; }
    S& operator=(const S& s) { std::cout << "Copy assignment\n"; return *this = S(s); }
    S& operator=(S&& s) { std::cout << "Move assignment\n"; std::swap(a, s.a); return *this; }
};

int main() {
    auto order = [] (const S& s1, const S& s2) -> bool { return s1.a < s2.a; };
    auto inver = [] (const S& s1, const S& s2) -> bool { return s2.a < s1.a; };
    
    std::set<S, decltype(order)> set1;
    set1.emplace(1);
    set1.emplace(3);
    set1.emplace(2);
    
    for(auto&& s : set1) {
        std::cout << s.a << " ";
    }
    std::cout << "\n";
    
    std::set<S, decltype(inver)> set2{set1.begin(), set1.end()};
    
    for(auto&& s : set2) {
        std::cout << s.a << " ";
    }
    std::cout << "\n";
    
    return 0;
}

Which prints the following output:

Constructor
Constructor
Constructor
1 2 3 
Copy constructor
Copy constructor
Copy constructor
3 2 1

Would it be possible to use the move constructor instead of the copy constructor? How?
If this is not possible, what would be a good strategy to solve my problem?
Should I just keep my objects in a data structure that doesn’t invalidate pointers and has constant-time direct access (such as a large vector that is never resized — or else, please suggest a more appropriate structure) and only sort their indices?

>Solution :

You can use std::set::extract (with std::move and std::emplace) in the following way:

std::set<S, decltype(inver)> set2;
auto it = set1.begin();
while (it != set1.end())
{
    auto tmp = it;
    it++;   // increment `it` while it is still valid
    set2.emplace(std::move(set1.extract(tmp).value()));  // this will invalidate `tmp`, but we not longer need it for the loop
}

Note that we use a tmp iterator for extract (which will invalidate it) while incrementing it which is used in the loop beforehand, while it is still valid.

Live demo

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading