Почему мы не можем использовать `std :: multiset` с пользовательским лямбда сравнения в качестве значения` std :: map`? - PullRequest
8 голосов
/ 02 июня 2019

Это следующий вопрос Как обеспечить собственный компаратор для `std :: multiset` без перегрузки` operator () `,` std :: less`, `std :: большее`?

и я попытался решить следующим образом.

Basic

Можно предоставить пользовательскую лямбда-функцию сравнения (начиная с ) с std::multiset члена класса следующим образом:

#include <iostream>
#include <set>

const auto compare = [](int lhs, int rhs) noexcept { return lhs > rhs; };
struct Test
{
    std::multiset<int, decltype(compare)> _set{compare};
    Test() = default;
};

Достаточно просто.

Моя ситуация

Член Test класса

std::map<std::string, std::multiset<int, /* custom compare */>> scripts{};

Я пытался использовать std::multiset с пользовательскими

  • функтор Compare (кейс - 1)
  • std::greater<> (кейс - 2)
  • лямбда-функция (кейс - 3)

Первые два варианта успешны. Но в случае лямбды как пользовательской функции сравнения это не сработало. Вот MCVC: https://godbolt.org/z/mSHi1p

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

const auto compare = [](int lhs, int rhs) noexcept { return lhs > rhs; };
class Test
{
private:
    struct Compare
    {
        bool operator()(const int lhs, const int rhs) const noexcept { return lhs > rhs; }
    };

private:
    // std::multiset<int, Compare> dummy;                      // works fine
    // std::multiset<int, std::greater<>> dummy;               // works fine
    std::multiset<int, decltype(compare)> dummy{ compare };    // does not work
    using CustomMultiList = decltype(dummy);

public: 
    std::map<std::string, CustomMultiList> 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 << '\n';
    }
}

Сообщение об ошибке:

error C2280 : '<lambda_778ad726092eb2ad4bce2e3abb93017f>::<lambda_778ad726092eb2ad4bce2e3abb93017f>(void)' : attempting to reference a deleted function
note: see declaration of '<lambda_778ad726092eb2ad4bce2e3abb93017f>::<lambda_778ad726092eb2ad4bce2e3abb93017f>'
note: '<lambda_778ad726092eb2ad4bce2e3abb93017f>::<lambda_778ad726092eb2ad4bce2e3abb93017f>(void)' : function was explicitly deleted
note: while compiling class template member function 'std::multiset<int,const <lambda_778ad726092eb2ad4bce2e3abb93017f>,std::allocator<int>>::multiset(void)'
note: see reference to function template instantiation 'std::multiset<int,const <lambda_778ad726092eb2ad4bce2e3abb93017f>,std::allocator<int>>::multiset(void)' being compiled
note: see reference to class template instantiation 'std::multiset<int,const <lambda_778ad726092eb2ad4bce2e3abb93017f>,std::allocator<int>>' being compiled

Звучит так, как будто я попытался построить по умолчанию лямбду, , которая невозможна до .

Если это дело , где это произошло ? Можно ли решить эту проблему с помощью лямбда-функции сравнения в пределах от до ?

Ответы [ 2 ]

8 голосов
/ 04 июля 2019

Звучит так, как будто я попытался построить по умолчанию лямбду, что невозможно до .Если это дело , где это произошло ?

Да .Это именно то, что произошло здесь и из-за вызова std::map::operator[] на линии (-ях)

t.scripts["Linux"].insert(5);
//       ^^^^^^^^^

Давайте рассмотрим подробнее.Вышеуказанный вызов приведет к вызову следующей перегрузки, поскольку ключ является временным std::string, созданным из const char*.

T& operator[]( Key&& key );

Поскольку C ++ 17, это эквивалентно :

return this->try_emplace(
    std::move(key)).first  ->  second;
//               key_type    mapped_type
//               ^^^^^^^^    ^^^^^^^^^^^
//                  |           |
//                  |           |
//             (std::string)  (std::multiset<int, decltype(compare)>)
//                  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//                  |           |                               (default-construction meaning)
//                  |       default-construction -->   std::multiset<int, decltype(compare)>{}
//               move-construction                                                          ^^

, где key_type (т. Е. Временно построенный std::string из const char*) должен быть Move Construtible , что происходитотлично.

mapped_type (то есть std::multiset<int, decltype(compare)>) должно быть конструкция по умолчанию ed в первую очередь, и это требует, чтобы лямбда сравнения была также по умолчаниюпостроен.С cppreference.com :

ClosureType :: ClosureType ()

ClosureType() = delete;   (until C++14)
ClosureType() = default;  (since C++20)(only if no captures are specified)

Типы закрытия не DefaultConstructible .Типы замыкания имеют удаленный (до C ++ 14) конструктор по умолчанию (с C ++ 14).(until C++20)


Если захват не указан, тип закрытия имеет конструктор по умолчанию .В противном случае у него нет конструктора по умолчанию (это включает случай, когда есть захват по умолчанию, даже если он на самом деле ничего не захватывает).(since C++20)

Это означает, что конструкция по умолчанию типа лямбда-замыкания недоступна в C ++ 17 (именно на это жалуется ошибка компилятора).

С другой стороны, захваты не указаны (то есть лямбда-выражения без состояния) в compare лямбда-выражениях, и, следовательно, они могут быть явно установлены по умолчанию компиляторами, которые поддерживают C ++20 стандарт.


Возможно ли решить эту проблему с помощью лямбда-функции сравнения в пределах до ?

Не используя std::map::operator[] (как объяснено выше), но Да , как у @ JohnZwinck'sупоминается в его ответе.Я хотел бы объяснить, как это работает.

Один из конструкторов 1 из std::multiset предоставляет возможность передать компараторobject.

template< class InputIt >
multiset( InputIt first, InputIt last,
          const Compare& comp = Compare(),
//        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
          const Allocator& alloc = Allocator() );

В то же время, конструктор копирования и конструктор перемещения для типа лямбда-замыкания были по умолчанию с C ++ 14 .Это означает, что если у нас есть возможность предоставить лямбду в качестве первого аргумента 2 (путем копирования или перемещения), это будет Basic случай, что показано в вопросе.

std::multiset<int, decltype(compare)> dummy{ compare };            // copying
std::multiset<int, decltype(compare)> dummy{ std::move(compare) }; // moving

К счастью, C ++ 17 представил функцию-член std :: map :: try_emplace

template <class... Args>
pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);

byкоторый можно передать лямбду вышеупомянутым конструкторам 1 из std::multiset в качестве первого аргумента 2 , как показано выше.Если мы превратим это в функцию-член класса Test, элементы могут быть вставлены в CustomMultiList (то есть значения) карты scripts.

Решение будет выглядеть так же, каксвязанный пост, потому что я написал этот ответ после того, как задал этот вопрос!)

( см. в прямом эфире )

#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[]
    // latest version of GCC has already included this change
    //t.scripts["Linux"].insert(5);
    //t.scripts["Linux"].insert(8);
    //t.scripts["Linux"].insert(0);

    return 0;
}
4 голосов
/ 02 июня 2019

Чтобы сделать это в одну строку, вам нужно что-то вроде этого:

t.scripts.try_emplace("Linux", compare).first->second.insert(5);

Это потому, что лямбда compare должна быть передана в конструктор вашего multiset.В противном случае нет объекта сравнения, и multiset не может быть построен.

Демо: https://godbolt.org/z/rVb3-D

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...