Search code examples
c++c++11lambdamultimapcustom-compare

How to provide custom comparator for `std::multiset` without overloading `operator()`, `std::less`, `std::greater`?


I want a custom comparator for the following code. However, I am not allowed to overload operator(), std::less, std::greater.

I tried to achieve this using lambda but gcc won't allow me to use auto as a non-static member. Any other way to make this work?

#include <iostream>
#include <map>
#include <set>

class Test 
{
public:
    // bool operator () (const int lhs, const int rhs) { // not allowed
    //     return lhs > rhs;
    // };    
    using list = std::multiset<int  /*, Test*/>;
    std::map<const char*, list> scripts;
};

int main() 
{
    Test t;
    t.scripts["Linux"].insert(5);
    t.scripts["Linux"].insert(8);
    t.scripts["Linux"].insert(0);

    for (auto a : t.scripts["Linux"]) {
        std::cout << a << std::endl;
    }

    std::cout << "end";
}

Edit: With lambdas

class Test 
{
  public:
    auto compare = [] (const int a, const int b) { return a < b;}
    using list = std::multiset<int, compare>;    //here
    std::map<const char*, list> scripts;
};

Error:

'auto' not allowed in non-static class member
 auto compare = [] (const int a, const int b) { return a < b;}

Solution

  • I want a custom comparator for the following code. However, I cannot overload operator(), std::less, std::greater.

    I assume that you are not allowed to overload operator() of the Test class, but could be that of other class. If so, create a internal private functor which overloads operator() and that could be part of the alias using list = std::multiset<int, Compare>;

    class Test
    {
    private:
        struct Compare
        {
            bool operator()(const int lhs, const int rhs) const /* noexcept */ { return lhs > rhs; }
        };
    
    public:
        using list = std::multiset<int, Compare>;
        std::map<std::string, list> scripts;
    };
    

    I tried to achieve these using lambdas but gcc won't allow me to use auto as a non-static member. Any other way to make this work?

    Update: After researching a while, I found a way to go which does work with a lambda function.

    The idea is to use the decltype of std::multiset with custom lambda compare as the key of the std::map scripts. In addition to that, provide a wrapper method for inserting the entries to the CustomMultiList.

    Complete example code: (See live)

    #include <iostream>
    #include <string>
    #include <map>
    #include <set>
    
    // provide a lambda compare
    const auto compare = [](int lhs, int rhs) noexcept { return lhs > rhs; };
    
    class Test
    {
    private:
        // make a std::multi set with custom compare function  
        std::multiset<int, decltype(compare)> dummy{ compare };
        using CustomMultiList = decltype(dummy); // use the type for values of the map 
    public:
        std::map<std::string, CustomMultiList> scripts{};
        // warper method to insert the `std::multilist` entries to the corresponding keys
        void emplace(const std::string& key, const int listEntry)
        {
            scripts.try_emplace(key, compare).first->second.emplace(listEntry);
        }
        // getter function for custom `std::multilist`
        const CustomMultiList& getValueOf(const std::string& key) const noexcept
        {
            static CustomMultiList defaultEmptyList{ compare };
            const auto iter = scripts.find(key);
            return iter != scripts.cend() ? iter->second : defaultEmptyList;
        }
    };
    
    
    int main()
    {
        Test t{};
        // 1: insert using using wrapper emplace method
        t.emplace(std::string{ "Linux" }, 5);
        t.emplace(std::string{ "Linux" }, 8);
        t.emplace(std::string{ "Linux" }, 0);
    
    
        for (const auto a : t.getValueOf(std::string{ "Linux" }))
        {
            std::cout << a << '\n';
        }
        // 2: insert the `CustomMultiList` directly using `std::map::emplace`
        std::multiset<int, decltype(compare)> valueSet{ compare };
        valueSet.insert(1);
        valueSet.insert(8);
        valueSet.insert(5);
        t.scripts.emplace(std::string{ "key2" }, valueSet);
    
        // 3: since C++20 : use with std::map::operator[]
        t.scripts["Linux"].insert(5);
        t.scripts["Linux"].insert(8);
        t.scripts["Linux"].insert(0);
    
        return 0;
    }
    

    Until lambda are not default constructable and copyable. But, the std::map::operator[] does requered the mapped_type to be copy constructible and default constructible. Hence the insertion to the value of the scripts map(i.e. to std::multiset<int, decltype(/*lambda compare*/)>) using subscription operator of std::map is only possible from from C++20.