JeJ*_*eJo 9 c++ lambda custom-compare multiset c++17
这是一个后续问题,询问 如何在不重载operator(),std :: less,std :: greater的情况下为std :: multiset提供自定义比较器?
并且我试图通过以下方式解决。
可以向类的成员提供自定义比较lambda函数(自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;
};
Run Code Online (Sandbox Code Playgroud)
很简单。
Test班上的成员是
std::map<std::string, std::multiset<int, /* custom compare */>> scripts{};
Run Code Online (Sandbox Code Playgroud)
我尝试使用std::multisetwith自定义
Compare(案例-1)std::greater<> (案例-2)前两个选项是成功的。但是,lambda作为自定义比较功能的情况却无法正常工作。这是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';
}
}
Run Code Online (Sandbox Code Playgroud)
错误信息:
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
Run Code Online (Sandbox Code Playgroud)
JeJ*_*eJo 10
听起来我尝试默认构造传递的lambda,直到c ++ 20才可能。如果是这样的话,它发生在哪里?
是的。这正是这里发生了什么,并由于呼叫std::map::operator[]在该行(S)
t.scripts["Linux"].insert(5);
// ^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
让我们来看看细节。上面的调用将导致以下重载的调用,因为密钥是std::string从中临时构造的const char*。
T& operator[]( Key&& key );
Run Code Online (Sandbox Code Playgroud)
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 ^^
Run Code Online (Sandbox Code Playgroud)
其中key_type(即std::string从临时构造const char*)应该是可移动构造的,发生的很好。
首先应使用默认构造 map_type(即std::multiset<int, decltype(compare)>),并且需要比较lambda也应采用默认构造。来自cppreference.com:
ClosureType :: ClosureType()
Run Code Online (Sandbox Code Playgroud)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)
这意味着,lambda闭包类型的默认构造在C ++ 17中不可用(这是编译器错误所抱怨的)。
另一方面,在那里的lambda中没有指定捕获(即无状态lambda)compare,因此支持C ++ 20标准的编译器可以将其显式默认为捕获。
不能使用std::map::operator[](如对上述理由解释),但是 是什么// @ JohnZwinck在他的回答提到了道路。我想解释一下,它是如何工作的。
构造函数1之一std::multiset提供了传递比较器对象的可能性。
template< class InputIt >
multiset( InputIt first, InputIt last,
const Compare& comp = Compare(),
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const Allocator& alloc = Allocator() );
Run Code Online (Sandbox Code Playgroud)
同时,自C ++ 14起,lambda闭包类型的copy构造函数和move构造函数已默认为默认值。就是说,如果我们有可能将lambda作为第一个参数2(通过复制或移动它)来提供,那将是Basic情况,即问题所在。
std::multiset<int, decltype(compare)> dummy{ compare }; // copying
std::multiset<int, decltype(compare)> dummy{ std::move(compare) }; // moving
Run Code Online (Sandbox Code Playgroud)
幸运的是,C ++ 17引入了成员函数std :: map :: try_emplace
template <class... Args>
pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);
Run Code Online (Sandbox Code Playgroud)
这样,就可以将lambda传递给上述的构造函数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;
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
395 次 |
| 最近记录: |