簡體   English   中英

為什么我們不能將`std :: multiset`與自定義比較lambda一起用作`std :: map`的值?

[英]Why can not we use `std::multiset` with custom compare lambda as the value of a `std::map`?

這是一個后續問題如何為`std :: multiset`提供自定義比較器而不重載`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{};

我試着用自定義的std::multiset

  • 仿函數Compare (案例 - 1)
  • std::greater<> (案例-2)
  • lambda函數(case-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函數來解決這個問題

聽起來我試圖默認構造傳遞的lambda,這在之前是不可能的。 如果發生這種情況的話

是的 這正是這里發生的事情,由於在線路上調用了std::map::operator[]

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

讓我們來看看細節。 上面的調用將導致調用以下重載,因為鍵是從const char*構造的臨時std::string

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 (即來自const char*臨時構造的std::string )應該是可構造的 ,這很好。

mapped_type (即std::multiset<int, decltype(compare)> )應該首先默認構造 ed,並且需要比較lambda也應該是默認構造的。 來自cppreference.com

ClosureType :: ClosureType()

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

閉包類型不是DefaultConstructible Closure類型有一個刪除(直到C ++ 14)no(自C ++ 14)默認構造函數。 (until C++20)


如果未指定捕獲,則閉包類型具有默認的默認 構造函數。 否則,它沒有默認構造函數 (這包括存在capture-default的情況,即使它實際上沒有捕獲任何東西)。 (since C++20)

這意味着,在C ++ 17中沒有提供lambda閉包類型的默認構造(這就是編譯器錯誤所抱怨的)。

另一方面,在compare lambda中沒有指定捕獲 (即無狀態lambdas),因此它可以由支持C ++ 20標准的編譯器明確默認。


是否可以使用 范圍內的lambda compare函數來解決這個問題

不使用std::map::operator[]至於原因如上所述),但是什么// @ JohnZwinck在他的回答提到了道路。 我想解釋一下,它是如何工作的。

一個構造函數的1 std::multiset提供通過比較對象的可能性。

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

同時, 自C ++ 14以來 ,lambda閉包類型的復制構造函數和移動構造函數已被默認 這意味着,如果我們有可能提供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傳遞給std::multiset的上述構造函數1作為第一個參數2,如上所示。 如果我們將其轉換為Test類的成員函數,則可以將元素插入到scripts映射的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[]
    // 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;
}

要在一行中完成,你需要這樣的東西:

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

這是因為lambda compare必須傳遞給multiset的構造函數。 否則沒有比較對象,並且無法構造multiset

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM