template<class C2>
void merge(std::set<Key, C2, Allocator>& source);
template<class C2>
void merge(std::set<Key, C2, Allocator>&& source);
template<class C2>
void merge(std::multiset<Key, C2, Allocator>& source);
template<class C2>
void merge(std::multiset<Key, C2, Allocator>&& source);
// New, specialized versions for containers with the same key comparison function.
void merge(std::set<Key, Compare, Allocator>& source);
void merge(std::set<Key, Compare, Allocator>&& source);
void merge(std::multiset<Key, Compare, Allocator>& source);
void merge(std::multiset<Key, Compare, Allocator>&& source);
void std::set::merge(std::set<Key, Compare, Allocator>& source) {
auto comp = this->key_comp();
auto source_it = source.begin();
for (auto it = this->begin(); it != this->end(); ++it) {
if (source_it == source.end()) break;
if (comp(*it, *source_it)) continue;
this->insert(it, source.extract(source_it++));
}
while (source_it != source.end()) {
this->insert(this->end(), source.extract(source_it++));
}
}
}
while (source_it != source.end()) {
this->insert(this->end(), source.extract(source_it++));
}
}Is there something I'm missing, or is this actually possible?
If so, is it possible to retroactively append it to C++17, given how recent it is and how it's not really a change in the API but rather in implementation? It shouldn't cause API/ABI breakage either.
That's theoretically possible, but that implementation violates the current complexity requirements, because the first loop is O(A + B), whereas the required complexity is O(B log (A + B)) (where A = dest.size() and B = source.size()). If B is small compared to A, this will slow down the algorithm. (I think that can be fixed by using a tree traversal over A in the first loop instead of a linear scan, which should give O(min(A, B log (A + B)) + B) for the first loop, which is an improvement over the standard's algorithm if B is large compared to A and is sorted according to A's comparator, but is otherwise the same.)
The comparator can be stateful. How can you reliably know that they have the same behavior across the two containers?
On Friday, June 29, 2018 at 3:06:53 AM UTC-7, T. C. wrote:The comparator can be stateful. How can you reliably know that they have the same behavior across the two containers?I'm not sure what to think about that. Is it some static internal variable that changes upon every insert, or maybe a comparator that hashes each node's pointer and chooses between multiple comparisons? A stateful comparator just seems like a can of worms that results in weird behavior anywhere. The result of calling merge() or anything else should not change based on implementation, so a comparator that changes behavior depending on how many times it was constructed/called or what it was compared with or which order they were compared with should be considered undefined behavior. Also, the behavior of the comparator should not behave differently between container instances, or else something like moving or copying between containers would invalidate the invariants of being sorted. In fact, for that reason alone, I don't think there is ever a good use for a comparator that changes behavior midway.
It doesn't need to change behavior midway. The point is that the behavior of the comparator is not solely determined by its type.template<class T> struct comp { bool ascending; bool operator()(const T& l, const T& r) const { return ascending? l < r : r < l; } };set<int, comp<int>> s1(comp<int>{true});set<int, comp<int>> s2(comp<int>{false});s1.merge(s2);
void std::set::merge(std::set<Key, C2, Allocator>& source) {
auto comp = this->key_comp();
auto dest_it = this->root_it;
auto prev_source_it = source.end();
for (auto source_it = source.begin(); source_it != source.end(); ) {
// If this is the first iteration or the source is not sorted via comp, start searching from the root.
// Otherwise, start searching from where the previous source was last inserted.
auto search_hint_it = (prev_source_it != source.end() && comp(*prev_source_it, *source_it)) ? dest_it : this->root_it;
auto insert_hint_it = this->lower_bound_hint(search_hint_it, *source_it);
this->insert(insert_hint_it, source.extract(source_it++));
dest_it = insert_hint_it;
}
}
On Thursday, June 28, 2018 at 1:15:57 PM UTC-7, Richard Smith wrote:That's theoretically possible, but that implementation violates the current complexity requirements, because the first loop is O(A + B), whereas the required complexity is O(B log (A + B)) (where A = dest.size() and B = source.size()). If B is small compared to A, this will slow down the algorithm. (I think that can be fixed by using a tree traversal over A in the first loop instead of a linear scan, which should give O(min(A, B log (A + B)) + B) for the first loop, which is an improvement over the standard's algorithm if B is large compared to A and is sorted according to A's comparator, but is otherwise the same.)Ah, true, since it's a tree anyways, tree traversal to the next hint would be better. Out of curiosity, how did you get O(min(A, B log (A + B)) + B) as a complexity?
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.