Search code examples
c++c++11stlshared-ptrcomparator

Is there an STL comparator for std::set (or std::map) with shared_ptr keys that provides value-based lookups? What exactly does std::owner_less do?


I have a std::map with shared_ptr<T> keys, and I need it to use the actual value (of type T, i.e. *key) for lookups, not the value of the shared pointer itself.

I know I can write my own custom comparator (as I've done below), but I was wondering if the STL supplies a comparator specifically for this purpose.

To demonstrate what I'm talking about, I created this simple example that uses a std::set of strings (I've also put it on GitHub as a gist):

#include <set>
#include <string>
#include <memory>
#include <iostream>
#include <functional>

template< typename T >
struct shared_ptr_comparator {
  bool operator()(const std::shared_ptr<T> &a, const std::shared_ptr<T> &b) const {
    return std::less<T>()(*a, *b);
  }
};

void ptr_set_with_custom_comparator() {
    std::set< std::shared_ptr<std::string>, shared_ptr_comparator<std::string> > ptr_set;

    ptr_set.insert(std::make_shared<std::string>("world"));
    ptr_set.insert(std::make_shared<std::string>("hello"));
    ptr_set.insert(std::make_shared<std::string>("abc"));

    for(auto const& entry : ptr_set) {
        std::cout << *entry << std::endl;
    }
}

void ptr_set_with_owner_less() {
    std::set< std::shared_ptr<std::string>, std::owner_less<std::shared_ptr<std::string>> > ptr_set;

    ptr_set.insert(std::make_shared<std::string>("world"));
    ptr_set.insert(std::make_shared<std::string>("hello"));
    ptr_set.insert(std::make_shared<std::string>("abc"));

    for(auto const& entry : ptr_set) {
        std::cout << *entry << std::endl;
    }
}

void raw_set() {
    std::set<std::string> raw_set;

    raw_set.insert("world");
    raw_set.insert("hello");
    raw_set.insert("abc");

    for(auto const& entry : raw_set) {
        std::cout << entry << std::endl;
    }
}

int main() {
    std::cout << "A basic set of strings:" << std::endl;
    raw_set();
    std::cout << std::endl;

    std::cout << "A set of shared_ptr<string>s with owner_less as the comparator:" << std::endl;
    ptr_set_with_owner_less();
    std::cout << std::endl;

    std::cout << "A set of shared_ptr<string>s with the comparator shared_ptr_comparator:" << std::endl;
    ptr_set_with_custom_comparator();

    return 0;
}

The code above can be complied with clang++ -Wall -std=c++11. Here's the output:

A basic set of strings:
abc
hello
world

A set of shared_ptr<string>s with owner_less as the comparator:
world
hello
abc

A set of shared_ptr<string>s with the comparator shared_ptr_comparator:
abc
hello
world

Here, a sorted ordering when iterating and printing the contents std::set implies that the _actual underlying values) are being compared. A quick overview of the example above:

  • The function raw_set just uses set<string> (doesn't use shared_ptr), and is present for reference.

  • I am able to achieve what I want with my hand-written shared_ptr_comparator. The function ptr_set_with_custom_comparator which utilizes it, works as expected.

  • The function ptr_set_with_owner_less did not work as expected. Does owner_less (or owner_before) rely on the addresses/values of the pointers themselves?

I have two questions:

  • Does anything equivalent to shared_ptr_comparator (defined in the program above), exist in the STL? I ask because the comparator I wrote seems like a really common use case, and I would be very surprised if the STL didn't have anything equivalent to it.

  • What exactly does owner_less and owner_before (which it calls) do? Do they simply check for equivalence of the underlying pointers? I'm not sure if I'm using it right.

Thanks in advance for any answers to this question.


Solution

  • Does anything equivalent to shared_ptr_comparator (defined in the program above), exist in the STL? I ask because the comparator I wrote seems like a really common use case, and I would be very surprised if the STL didn't have anything equivalent to it.

    I'm suprised too, but no, STL doesn't have built-in comparator for pointers that works this way. However there's better way to implement it which allows passing any pointer.

    template<typename T, typename comp_t>
    bool ptr_compare(T lhs, T rhs, comp_t comp) {
        return comp(*lhs, *rhs);
    }
    

    You can call it that way:

    ptr_compare(a_ptr, b_ptr, std::less<int>())
    

    And if you want a version compatible with STL containers:

    template<typename T>
    bool ptr_less(T lhs, T rhs) {
        return std::less<decltype(*lhs)>()(*lhs, *rhs);
    }
    

    What exactly does owner_less and owner_before (which it calls) do? Do they simply check for equivalence of the underlying pointers? I'm not sure if I'm using it right.

    std::owner_less doesn't compare by value, but by owner, so it isn't relevant to your problem.

    std::shared_ptr<T>::owner_before is called by std::owner_less to find out the ordering.