为什么我们不能使用带有自定义比较 lambda 的 std::multiset 作为 std::map 的值?
Why can not we use `std::multiset` with custom compare lambda as the value of a `std::map`?
这是被问的后续问题
我试过用下面的方式解决
基本
可以向 class 成员的 std::multiset
提供自定义比较 lambda 函数(因为 c++11),如下所示:
#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
class的成员是
std::map<std::string, std::multiset<int, /* custom compare */>> scripts{};
我尝试将 std::multiset
与自定义
一起使用
- 函子
Compare
(案例 - 1)
std::greater<>
(案例 - 2)
- lambda 函数(案例 - 3)
前两个选项成功。但是 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';
}
}
错误信息:
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
听起来我试图默认构造传递的 lambda,which is not possible until c++20。
如果是这样的话它发生在哪里?
是否可以在 c++11 到 c++17 的范围内使用 lambda 比较函数 来解决这个问题?
要在一行中完成,您需要这样的东西:
t.scripts.try_emplace("Linux", compare).first->second.insert(5);
这是因为 lambda compare
必须传递给 multiset
的构造函数。否则没有比较对象,无法构造multiset
。
It sounds like I tried to default construct the passed lambda, which
is not possible until c++20. If that the case where has it happened?
是。这正是由于 std::map::operator[]
在
行的调用而发生的事情
t.scripts["Linux"].insert(5);
// ^^^^^^^^^
让我们详细了解一下。上面的调用将导致调用以下重载,因为密钥是临时 std::string
从 const char*
.
构造的
T& operator[]( Key&& key );
因为 C++17 this is equivalent to:
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(即从 const char*
临时构建的 std::string
)应该是 move constructible ,
碰巧很好。
mapped_type(即std::multiset<int, decltype(compare)>
)应该是default construct ed first and that requires the compare lambda should be also default constructed. From cppreference.com:
ClosureType::ClosureType()
ClosureType() = delete; (until C++14)
ClosureType() = default; (since C++20)(only if no captures are specified)
Closure types are not DefaultConstructible. Closure types have a
deleted (until C++14)no (since C++14) default constructor. (until C++20)
If no captures are specified, the closure type has a defaulted default
constructor. Otherwise, it has no default constructor (this includes
the case when there is a capture-default, even if it does not actually
capture anything). (since C++20)
这意味着,lambda 闭包类型的默认构造在 C++17 中不可用(这就是编译器错误所抱怨的)。
另一方面,在 compare
lambda 中没有指定 捕获(即无状态 lambda),因此编译器可以显式默认它支持C++20标准。
Is it possible to solve this using a lambda compare function within
the scope of c++11 till c++17?
不是通过使用std::map::operator[]
(正如上面解释的原因),而是是,@JohnZwinck的方式在他的回答中提到。我想解释一下,它是如何工作的。
std::multiset
的1构造函数之一提供了传递比较器对象的可能性。
template< class InputIt >
multiset( InputIt first, InputIt last,
const Compare& comp = Compare(),
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const Allocator& alloc = Allocator() );
同时,lambda闭包类型的复制构造函数和移动构造函数已经defaulted since C++14。这意味着,如果我们有可能提供 lambda 作为第一个参数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);
通过它可以将 lambda 作为第一个传递给 std::multiset
的 above-mentioned 构造函数1 argument2 如上所示。如果我们将其转换为 Test
class 的成员函数,则可以将元素插入到 scripts
映射的 CustomMultiList
(即值)中。
解决方案看起来像(与链接 post 相同,因为我在问这个问题后写了那个答案!)
(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[]
// 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;
}
这是被问的后续问题
我试过用下面的方式解决
基本
可以向 class 成员的 std::multiset
提供自定义比较 lambda 函数(因为 c++11),如下所示:
#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
class的成员是
std::map<std::string, std::multiset<int, /* custom compare */>> scripts{};
我尝试将 std::multiset
与自定义
- 函子
Compare
(案例 - 1) std::greater<>
(案例 - 2)- lambda 函数(案例 - 3)
前两个选项成功。但是 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';
}
}
错误信息:
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
听起来我试图默认构造传递的 lambda,which is not possible until c++20。
如果是这样的话它发生在哪里? 是否可以在 c++11 到 c++17 的范围内使用 lambda 比较函数 来解决这个问题?
要在一行中完成,您需要这样的东西:
t.scripts.try_emplace("Linux", compare).first->second.insert(5);
这是因为 lambda compare
必须传递给 multiset
的构造函数。否则没有比较对象,无法构造multiset
。
It sounds like I tried to default construct the passed lambda, which is not possible until c++20. If that the case where has it happened?
是。这正是由于 std::map::operator[]
在
t.scripts["Linux"].insert(5);
// ^^^^^^^^^
让我们详细了解一下。上面的调用将导致调用以下重载,因为密钥是临时 std::string
从 const char*
.
T& operator[]( Key&& key );
因为 C++17 this is equivalent to:
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(即从 const char*
临时构建的 std::string
)应该是 move constructible ,
碰巧很好。
mapped_type(即std::multiset<int, decltype(compare)>
)应该是default construct ed first and that requires the compare lambda should be also default constructed. From cppreference.com:
ClosureType::ClosureType()
ClosureType() = delete; (until C++14) ClosureType() = default; (since C++20)(only if no captures are specified)
Closure types are not DefaultConstructible. Closure types have a deleted (until C++14)no (since C++14) default constructor.
(until C++20)
If no captures are specified, the closure type has a defaulted default constructor. Otherwise, it has no default constructor (this includes the case when there is a capture-default, even if it does not actually capture anything).
(since C++20)
这意味着,lambda 闭包类型的默认构造在 C++17 中不可用(这就是编译器错误所抱怨的)。
另一方面,在 compare
lambda 中没有指定 捕获(即无状态 lambda),因此编译器可以显式默认它支持C++20标准。
Is it possible to solve this using a lambda compare function within the scope of c++11 till c++17?
不是通过使用std::map::operator[]
(正如上面解释的原因),而是是,@JohnZwinck的方式在他的回答中提到。我想解释一下,它是如何工作的。
std::multiset
的1构造函数之一提供了传递比较器对象的可能性。
template< class InputIt >
multiset( InputIt first, InputIt last,
const Compare& comp = Compare(),
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const Allocator& alloc = Allocator() );
同时,lambda闭包类型的复制构造函数和移动构造函数已经defaulted since C++14。这意味着,如果我们有可能提供 lambda 作为第一个参数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);
通过它可以将 lambda 作为第一个传递给 std::multiset
的 above-mentioned 构造函数1 argument2 如上所示。如果我们将其转换为 Test
class 的成员函数,则可以将元素插入到 scripts
映射的 CustomMultiList
(即值)中。
解决方案看起来像(与链接 post 相同,因为我在问这个问题后写了那个答案!)
(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[]
// 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;
}