Как обеспечить собственный компаратор для `std :: multiset` без перегрузки` operator () `,` std :: less`, `std :: большее`? - PullRequest
3 голосов
/ 31 мая 2019

Я хочу собственный компаратор для следующего кода.Однако мне не разрешено перегружать operator(), std::less, std::greater.

Я пытался добиться этого с помощью лямбды, но gcc не позволит мне использовать auto в качестве нестатического члена.Любой другой способ заставить это работать?

#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";
}

Редактировать : С лямбдами

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;
};

Ошибка:

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

Ответы [ 3 ]

4 голосов
/ 01 июня 2019

Я хочу собственный компаратор для следующего кода. Однако я не могу перегрузка operator(), std::less, std::greater.

Я предполагаю, что вам не разрешено перегружать operator() класса Test, но это может быть класс другого класса. Если это так, создайте внутренний функтор private, который перегружает operator() и который может быть частью псевдонима 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;
};

Я пытался достичь этих , используя лямбды , но gcc не позволит мне использовать auto как нестатический член. Есть ли другой способ заставить это работать?

Обновление : После некоторого исследования я нашел способ, который работает с лямбда-функцией.

Идея состоит в том, чтобы использовать decltype из std::multiset с пользовательским лямбда-сравнением в качестве ключа std::map сценариев . В дополнение к этому, предоставьте метод-оболочку для вставки записей в CustomMultiList.

Полный пример кода: ( Смотрите в прямом эфире )

#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;
}

До тех пор, пока лямбда не по умолчанию могут быть созданы и скопированы . Но std :: map :: operator [] требует, чтобы mapped_type был копируемый и по умолчанию конструктивно . Следовательно, вставка в значение карты scripts (т.е. в std::multiset<int, decltype(/*lambda compare*/)>) с использованием оператора подписки std::map возможна только из C ++ 20.

2 голосов
/ 01 июня 2019

Существует проблема с вашим подходом, даже если вы можете определить лямбду так, как вы хотите.Взгляните на объявление multiset :

template<
    class Key,
    class Compare = std::less<Key>,
    class Allocator = std::allocator<Key>
> class multiset;

Обратите внимание, как каждый параметр шаблона является типом (используя ключевое слово class).Теперь посмотрим, как вы пытались определить свой список:

using list = std::multiset<int, compare>;
                            ^      ^
                          type   value

Первый параметр хорош, но второй - несоответствие.Параметр Compare должен быть типом, а не объектом.Одним из общих способов разрешения такой ситуации является замена compare на decltype(compare), но, похоже, это не то, что вам нужно (плюс это проблематично для лямбда-типов).Кажется, вы хотите, чтобы построенный по умолчанию list использовал compare вместо простого созданного по умолчанию объекта того же типа.

Итак, вам нужен класс, чей построенный по умолчанию объект реализует operator() вспособ, который дает заказ, который вы хотите.Поскольку мы имеем дело с int, в стандартной библиотеке есть несколько готовых типов для этой цели, а именно std::less и std::greater.

using list = std::multiset<int, std::greater<int>>;

Однако я не могу перегрузить оператор(), std :: less, std :: большее.

Хмм ... это говорит о том, что пример кода мог быть упрощен, поскольку перегрузка не требуется.Хорошо, давайте предположим, что список имеет некоторый тип, с которым сложнее работать, скажем:

class I { /* Internals not important for this example. */ };
using list = std::multiset<I, ???>;

Если вам разрешено изменять I, тогда самый простой подход может быть определением operator> (илиoperator<) для объектов типа I.Поскольку std::greater (или std::less) использует этот оператор, вы получаете желаемый заказ из стандартного шаблона, не перегружая его.

Если вам не разрешено изменять I, то я думаю, что выосталось написать собственный объект функции , так как это одна из ситуаций, когда лямбда неадекватна.К счастью, классы, реализующие функциональные объекты, легко написать;Лямбды вытесняют их в других ситуациях, главным образом потому, что лямбда-синтаксис имеет тенденцию быть более удобным.

struct CompareI {
    bool operator() (const I & lhs, const I & rhs) const { return /* fill this in */; }
};
using list = std::multiset<I, CompareI>;

Хотя это определяет operator(), это не перегрузка.Поэтому оно должно удовлетворять предъявляемым к вам требованиям.

2 голосов
/ 31 мая 2019

Вы можете использовать указатель функции функции сравнения в конструкторе:

main.cpp

#include <iostream>
#include <set>

using compType=bool(*)(int lhs, int rhs);

bool custom_compare_function(int lhs, int rhs)
{
    return lhs>rhs;
}


using list = std::multiset<int,compType>;
int main() {
    list l(&custom_compare_function);
    l.insert(1);
    l.insert(4);
    l.insert(2);
    for (auto& item: l) std::cout<<item<<std::endl;
}

производит вывод

$ g++ main.cpp 
$ ./a.out 
4
2
1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...