如何在不重载"operator()"、"std::less"、"std

How to provide custom comparator for `std::multiset` without overloading `operator()`, `std::less`, `std::greater`?

本文关键字:quot std less operator 重载      更新时间:2023-10-16

我想要以下代码的自定义比较器。但是,我不允许超载operator()std::lessstd::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";
}

编辑:使用λ

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()std::lessstd::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;
};

我试图使用 lambda 实现这些,但gcc不允许我使用 自动作为非静态成员。还有其他方法可以做到这一点吗?

更新:经过一段时间的研究,我找到了一种确实有效的方法带有lambda函数。

这个想法是使用自定义 lambda 比较std::multisetdecltype作为std::map脚本的键。除此之外,还提供用于将条目插入CustomMultiList的包装器方法。

完整示例代码:(查看实时)

#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 不是默认的可构造和可复制的。但是,std::map::operator[]确实要求mapped_type可复制的,默认是可构造的。因此,使用std::map订阅运算符插入scripts映射的值(即std::multiset<int, decltype(/*lambda compare*/)>)只能从 C++20 开始

即使你可以按照你想要的方式定义一个lambda,你的方法也存在问题。看看multiset声明:

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 类型来说,这是有问题的)。您似乎希望使用默认构造listcompare,而不仅仅是相同类型的默认构造对象。

因此,您需要的是一个类,其默认构造对象以提供所需顺序的方式实现operator()。由于我们正在处理int,标准库为此有一些现成的类型,即std::lessstd::greater

using list = std::multiset<int, std::greater<int>>;

但是,我不能重载运算符(),标准::更少,标准::更大。

嗯......这表明示例代码可能过于简化,因为不需要重载。好吧,让我们假设列表是某种更难处理的类型,比如:

class I { /* Internals not important for this example. */ };
using list = std::multiset<I, ???>;

如果允许您修改I,那么最简单的方法可能是为I类型的对象定义operator>(或operator<)。由于std::greater(或std::less)使用此运算符,因此您可以从标准模板中获得所需的订单,而不会重载它。

如果您不被允许修改I,那么我认为您只能编写自己的函数对象,因为这是 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(),但它不是重载。所以它应该满足给你的要求。

您可以在构造函数中使用比较函数的函数指针:

主.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