9

这是关于如何在不重载 `operator()`、`std::less`、`std::greater` 的情况下为`std::multiset` 提供自定义比较器的后续问题

我试图通过以下方式解决。

基本的

可以为类的成员提供自定义比较 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{};

我尝试使用std::multisetwith custom

  • 函子Compare(案例 - 1)
  • std::greater<> (案例 - 2)
  • 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,这在之前是不可能的

如果是这样的话,它发生在哪里?是否可以在的范围内使用 lambda compare 函数来解决这个问题

4

2 回答 2

11

之前是不可能的。如果是这样的话,它发生在哪里

的。这正是这里发生的事情,并且由于std::map::operator[]在线的调用

t.scripts["Linux"].insert(5);
//       ^^^^^^^^^

让我们仔细看看。上述调用将导致调用以下重载,因为键是临时std::string从 构造的const char*

T& operator[]( Key&& key );

由于C++17,这相当于

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                                                          ^^

其中key_type(即临时std::string从构造const char*)应该是可移动的,这很好。

mapped_type (ie ) 应该首先是默认构造的,并且需要比较 lambda 也应该是默认构造的。来自cppreference.comstd::multiset<int, decltype(compare)>

闭包类型::闭包类型()

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

闭包类型 不是 DefaultConstructible。闭包类型有一个已删除的(C++14 前)无(C++​​14 起)默认构造函数。 (until C++20)


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

这意味着,C++17 中不提供 lambda 闭包类型的默认构造(这就是编译器错误所抱怨的)。

另一方面,没有在 lambda中指定捕获(即无状态 lambda),compare因此支持 C++20 标准的编译器可以显式默认它。


是否可以在的范围内使用 lambda 比较函数来解决这个问题

不是通过使用std::map::operator[](如上所述的原因),而是 Yes,@JohnZwinck 在他的回答中提到的方式。我想解释一下,它是如何工作的。

构造函数1之一std::multiset提供了传递比较器对象的可能性。

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

同时,lambda 闭包类型的复制构造函数和移动构造函数自 C++14 以来已默认。这意味着,如果我们有可能将 lambda 作为第一个参数2提供(通过复制或移动它),那将是问题中显示的基本情况。

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

通过它可以将 lambda作为第一个参数2传递给上述构造函数1,如上所示。如果我们将其变形到类的成员函数中,则可以将元素插入到映射的(即值)中。std::multisetTestCustomMultiListscripts

解决方案看起来像(与链接的帖子相同,因为我在问这个问题后写了那个答案!)

见直播

#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;
}
于 2019-07-04T18:35:29.863 回答
4

要在一行中完成,您需要这样的东西:

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

这是因为 lambdacompare必须传递给multiset. 否则没有比较对象,multiset无法构造。

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

于 2019-06-01T22:54:21.847 回答