Почему мы не можем использовать `std::multiset` с пользовательским лямбда сравнения в качестве значения`std::map`?
Это дополнительный вопрос задаваемого пользовательского компаратора для набора без перегрузки operator(), std::less, std:: большее
и я попытался решить следующим образом.
основной
Можно предоставить пользовательскую функцию сравнения лямбда (начиная с C++11) для 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 works
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
Похоже, я пытался по умолчанию построить переданную лямбда, что невозможно до C++ 20.
Если это тот случай, где это произошло? Можно ли решить эту проблему с помощью функции лямбда-сравнения в пределах от C++11 до C++ 17?
1 ответ
Похоже, я пытался по умолчанию построить переданную лямбда, что невозможно до C++20. Если это тот случай, где это произошло?
Да Это именно то, что произошло здесь и из-за вызова 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*
) должен быть ходовым, что случается нормально.
Mapped_type (то есть std::multiset<int, decltype(compare)>
) сначала должна быть создана по умолчанию, и это требует, чтобы лямбда сравнения также была создана по умолчанию. С 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.
Можно ли решить эту проблему с помощью функции лямбда-сравнения в пределах от C++11 до C++17?
Не используя std::map::operator[]
(по причине, описанной выше), но да, то, что @JohnZwinck's упомянул в своем ответе. Я хотел бы объяснить, как это работает.
Один из конструкторов 1 std::multiset
предоставляет возможность передать объект сравнения.
template< class InputIt >
multiset( InputIt first, InputIt last,
const Compare& comp = Compare(),
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const Allocator& alloc = Allocator() );
В то же время, конструктор копирования и конструктор перемещения для типа лямбда-замыкания были установлены по умолчанию начиная с C++ 14. Это означает, что если у нас есть возможность предоставить лямбду в качестве первого аргумента 2 (путем копирования или перемещения), это будет основной случай, который показан в вопросе.
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);
с помощью которого можно передать лямбда вышеупомянутым конструкторам 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;
}
Чтобы сделать это в одну строку, вам нужно что-то вроде этого:
t.scripts.try_emplace("Linux", compare).first->second.insert(5);
Это потому что лямбда compare
должен быть передан конструктору вашего multiset
, В противном случае нет объекта сравнения и multiset
не может быть построено.