MK.
MK.

Reputation: 34587

why is STL set comparator only specified via template?

The only way to supply custom comparator to the std::set container is via the template specialization. Which means that the comparator detail will leak to all users of the set. Is there a way around it and why such a strange decision choice was made? Shouldn't I be able to have a method which returns a set ordered to my liking but w/o everyone having to know what the ordering is?

Upvotes: 4

Views: 278

Answers (2)

Johan Lundberg
Johan Lundberg

Reputation: 27038

As pointed out in the comments (igor and dietmar) you can do this via std::function:

#include <set>
#include <functional>
#include <iostream>

template<typename T>
using Dynamic_set= std::set<T, std::function<bool(const T&, const T&)>>;

int main(int argc, char **argv)
{  
   auto absSet = Dynamic_set<int>([](const auto&a, const auto&b){ return abs(a)<abs(b) ;});
   absSet.emplace(1);
   absSet.emplace(2);
   absSet.emplace(-1);
   std::cout << absSet.size() << std::endl;
}

prints 2. Note that using auto as above avoids most vexing parse issues.

More general:

template<typename Key, typename Allocator = std::allocator<Key>>
using Dynamic_set= std::set<Key, std::function<bool(const Key&, const Key&)>, Allocator>;

Live demo

Upvotes: 2

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153975

For many uses, the default choice of comparison, i.e., using std::less<T> which uses a < b works very well. This comparison doesn't do any dynamic dispatch which would allow a choice of order without affecting the type because the dynamic dispatch would likely cause a performance degradation. Adding a dynamic dispatch certainly caused a performance degradation at the time the interface was defined and I'd think it is still true and unlikely to go away entirely.

If you want to choose the order without affecting the type you can use std::function<bool(T const&, T const&)> (or std::function<bool(T, T)> if T happens to be a cheap to copy type like the built-in types). You'd need to specify the comparison object as a constructor argument in this case. For example:

std::set<int, std::function<bool(int, int)>> values{std::less<int>()};

Note that the above code uses curlies as otherwise it would be a function declaration rather than an object definition. Also note that assigning a std:set<int, std::function<bool(int, int)>> to another one with different comparison function will not work: the code would compile but the assignment will create a set which is incorrectly ordered. For example:

#include <functional>
#include <iostream>
#include <set>

int main() {
    std::set<int, std::function<bool(int, int)>> values1{std::less<int>()};
    values1.insert(1);
    values1.insert(2);
    values1.insert(3);
    std::set<int, std::function<bool(int, int)>> values2{std::greater<int>()};
    values2 = values1;
    for (auto v: values2) {
        std::cout << v << '\n';
    }
}

This prints the values in ascending order although it should print them in descending order.

Upvotes: 5

Related Questions