代码之家  ›  专栏  ›  技术社区  ›  JeJo

为什么不能使用带有自定义比较lambda的“std::multiset”作为“std::map”的值?

  •  0
  • JeJo  · 技术社区  · 5 年前

    这是一个被问到的后续问题 How to provide custom comparator for `std::multiset` without overloading `operator()`, `std::less`, `std::greater`?

    我试着用下面的方法来解决。

    可以提供自定义比较lambda函数(因为 )到 std::multiset

    #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

    std::map<std::string, std::multiset<int, /* custom compare */>> scripts{};
    

    我试着用 标准::多集

    • 函子 Compare (案例1)
    • std::greater<>
    • 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 直到 .

    发生在哪里 可以使用lambda比较函数来解决这个问题 在…的范围内 直到

    0 回复  |  直到 5 年前
        1
  •  10
  •   JeJo    5 年前

    听起来像是我试图默认构造传递的lambda . 如果是这样的话 ?

    是的 . 这正是因为 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                                                          ^^
    

    在哪里 钥匙类型 (即临时建造 标准::字符串 常量字符* )应该是 move constructible , 一切都很顺利。

    (即。 std::multiset<int, decltype(compare)> default construct 需要比较lambda的,也应该是默认构造的。从 cppreference.com :

    ClosureType() = delete;   (until C++14)
    ClosureType() = default;  (since C++20)(only if no captures are specified)
    

    不可默认构造 . 闭包类型具有 删除(直到C++ 14)NO(因为C++ 14)默认构造函数。 (until C++20)


    如果 如果未指定捕获,则闭包类型具有默认值 (这包括 存在捕获默认值的情况,即使它实际上不存在 捕捉任何东西)。 (since C++20)

    这意味着,在C++ 17中不可用的lambda闭包类型的默认构造(编译器错误正在抱怨)。

    已指定捕获 (即无状态lambda)在 compare LAMBDA在那里,因此它可以由支持C++ 20标准的编译器显式地默认。


    它是 可以使用lambda比较函数来解决这个问题 在内部 范围 直到

    不是通过使用 (至于上面解释的原因),但是 是的 ,就像JohnZwinck在回答中提到的那样。我想解释一下,这是怎么回事。

    1 属于 std::multiset 提供传递比较器对象的可能性。

    template< class InputIt >
    multiset( InputIt first, InputIt last,
              const Compare& comp = Compare(),
    //        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
              const Allocator& alloc = Allocator() );
    

    defaulted since C++14 . 这意味着,如果我们有可能提供lambda作为第一个参数 (通过复制或移动),它将是 案例,问题中的内容。

    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);
    

    1 标准::多集 2 Test 类中,可以将元素插入到 CustomMultiList scripts 地图。

    ( 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;
    }
    
        2
  •  4
  •   John Zwinck    5 年前

    要在一行中完成,您需要以下内容:

    t.scripts.try_emplace("Linux", compare).first->second.insert(5);
    

    compare 必须传递给 multiset . 否则就没有比较对象和 多集 无法构造。

    演示: https://godbolt.org/z/rVb3-D