如何在不重载 `operator()`、`std::less`、`std::greater` 的情况下为`std::multiset` 提供自定义比较器?
Posted
技术标签:
【中文标题】如何在不重载 `operator()`、`std::less`、`std::greater` 的情况下为`std::multiset` 提供自定义比较器?【英文标题】:How to provide custom comparator for `std::multiset` without overloading `operator()`, `std::less`, `std::greater`? 【发布时间】:2019-05-31 19:32:01 【问题描述】:我想要以下代码的自定义比较器。但是,我不允许超载operator()
、std::less
、std::greater
。
我尝试使用 lambda 实现此目的,但 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";
编辑:使用 lambda
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;
【问题讨论】:
"但是,我不能重载 operator()" 你的意思是它没有工作,或者你不被允许这样做? 您打算比较哪些类型?顺便说一句,比较 const char* 值不是一个好主意,因为这只是指针的比较;此外,使用相同的字符串文字,即“Linux”实际上可以产生两个不同的指针。我建议使用 std::string 作为 std::map 中的键 @HolyBlackCat 不允许 @Dmitry Kuzminov 哦。你是对的。我只是将一个示例程序放在一起,但感谢您指出这一点。 您在哪里尝试使 lambda 工作?这个不允许的“汽车”在哪里? (如果您想要一个更有用的答案,展示您迄今为止尝试过的内容是一个好主意。) 【参考方案1】:我想要以下代码的自定义比较器。然而,我不能 过载
operator()
,std::less
,std::greater
。
我假设您不允许重载 Test
类的 operator()
,但可以是其他类的重载。如果是这样,请创建一个内部 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;
;
我尝试使用 lambdas 来实现这些,但 gcc 不允许我使用 auto 作为非静态成员。还有其他方法可以完成这项工作吗?
更新:经过一段时间的研究,我找到了一种方法,该方法可以使用 lambda 函数†。
这个想法是使用std::multiset
的decltype
和自定义 lambda compare 作为std::map
脚本 的键。除此之外,提供一个包装方法,用于将条目插入CustomMultiList
。
完整示例代码:(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[]
t.scripts["Linux"].insert(5);
t.scripts["Linux"].insert(8);
t.scripts["Linux"].insert(0);
return 0;
† 直到c++20 lambda 不是default constructable and copyable。但是,std::map::operator[] 确实要求 mapped_type 为 copy constructible 和 default constructible。因此,使用std::map
的订阅运算符插入到scripts
映射的值(即std::multiset<int, decltype(/*lambda compare*/)>
)只能从C++20 开始。
【讨论】:
【参考方案2】:即使您可以按照自己的方式定义 lambda,您的方法也存在问题。看看the multiset
declaration:
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)
,但这似乎不是您想要的(另外它对于 lambda 类型是有问题的)。您似乎希望使用默认构造的 list
来使用 compare
而不仅仅是相同类型的默认构造对象。
所以你需要的是一个类,它的默认构造对象以一种给出你想要的顺序的方式实现operator()
。由于我们处理的是int
,因此标准库为此提供了一些现成的类型,即std::less
和std::greater
。
using list = std::multiset<int, std::greater<int>>;
但是,我不能重载 operator()、std::less、std::greater。
嗯...这表明示例代码可能被过度简化了,因为不需要重载。好的,假设列表是某种更难处理的类型,比如:
class I /* Internals not important for this example. */ ;
using list = std::multiset<I, ???>;
如果允许您修改I
,那么最简单的方法可能是为I
类型的对象定义operator>
(或operator<
)。由于std::greater
(或std::less
)使用此运算符,您可以从标准模板中获得所需的顺序,而无需重载它。
如果不允许您修改 I
,那么我认为您只能编写自己的 function object,因为这是 lambda 不足的一种情况。幸运的是,实现函数对象的类很容易编写; lambda 在其他情况下已经取代了它们,主要是因为 lambda 语法更方便。
struct CompareI
bool operator() (const I & lhs, const I & rhs) const return /* fill this in */;
;
using list = std::multiset<I, CompareI>;
虽然这定义了一个operator()
,但它不是一个重载。所以它应该满足给你的要求。
【讨论】:
【参考方案3】:可以在构造函数中使用比较函数的函数指针:
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
【讨论】:
一个简单的问题!在这种情况下我将如何初始化列表比较器函数std::map<string&, list>
?因为如果 key 不存在 map 将默认构造,它会将比较器初始化为 null 可能
@SumitDhingra 抱歉,您不能只为 map 的第一个参数编写比较器,这就是重点。请写一个正确的问题以获得更详细的答案。
@GemTaylor @phinz 答案list l(&custom_compare_function);
中的这一行初始化了多集比较器。正确的?但是如果我有类似std::map<string&, std::set<int, compType>
的东西,那么我该如何初始化multiset 的比较器呢?
你是对的,在这种情况下,地图不会默认构造。假设map
是您的映射变量,您可以通过比较map.find(<key>)
和map.end()
来检查现有键。如果键不存在,您可以使用map.emplace(...)
为地图构造一个新条目,并为构造函数使用自定义参数,请参阅cplusplus.com/reference/map/map以上是关于如何在不重载 `operator()`、`std::less`、`std::greater` 的情况下为`std::multiset` 提供自定义比较器?的主要内容,如果未能解决你的问题,请参考以下文章
重载operator<<运算符时第二个参数最好不能写成指向对象的指针
如何检测类型是否可以流式传输到std :: ostream?