【问题标题】:How to provide custom comparator for `std::multiset` without overloading `operator()`, `std::less`, `std::greater`?如何在不重载 `operator()`、`std::less`、`std::greater` 的情况下为`std::multiset` 提供自定义比较器?
【发布时间】: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 工作?这个不允许的“汽车”在哪里? (如果您想要一个更有用的答案,展示您迄今为止尝试过的内容是一个好主意。)

标签: c++ c++11 lambda multimap custom-compare


【解决方案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;
}

直到 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
      猜你喜欢
      • 2020-08-09
      • 1970-01-01
      • 2023-02-02
      • 1970-01-01
      • 2012-10-10
      • 2013-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多