如何在不重载 `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::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";

编辑:使用 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::lessstd::greater

我假设您不允许重载 Test 类的 operator(),但可以是其他类的重载。如果是这样,请创建一个内部 private 函子,它重载 operator() 并且可能是别名 using list = std::multiset&lt;int, Compare&gt;; 的一部分

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::multisetdecltype自定义 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_typecopy constructibledefault constructible。因此,使用std::map 的订阅运算符插入到scripts 映射的值(即std::multiset&lt;int, decltype(/*lambda compare*/)&gt;)只能从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::lessstd::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&gt;(或operator&lt;)。由于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】:

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

ma​​in.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&lt;string&amp;, list&gt;?因为如果 key 不存在 map 将默认构造,它会将比较器初始化为 null 可能 @SumitDhingra 抱歉,您不能只为 map 的第一个参数编写比较器,这就是重点。请写一个正确的问题以获得更详细的答案。 @GemTaylor @phinz 答案list l(&amp;custom_compare_function); 中的这一行初始化了多集比较器。正确的?但是如果我有类似std::map&lt;string&amp;, std::set&lt;int, compType&gt; 的东西,那么我该如何初始化multiset 的比较器呢? 你是对的,在这种情况下,地图不会默认构造。假设map 是您的映射变量,您可以通过比较map.find(&lt;key&gt;)map.end() 来检查现有键。如果键不存在,您可以使用map.emplace(...) 为地图构造一个新条目,并为构造函数使用自定义参数,请参阅cplusplus.com/reference/map/map

以上是关于如何在不重载 `operator()`、`std::less`、`std::greater` 的情况下为`std::multiset` 提供自定义比较器?的主要内容,如果未能解决你的问题,请参考以下文章

重载operator<<运算符时第二个参数最好不能写成指向对象的指针

我该如何重载''运算符?

如何检测类型是否可以流式传输到std :: ostream?

如果我重载了原始的“operator new”,如何调用它?

如何在具有动态大小数组的模板类中重载 operator=

如何将 std::map::operator= 与初始值设定项列表一起使用