This is a follow-up question of asked How to provide custom comparator for `std::multiset` without overloading `operator()`, `std::less`, `std::greater`?
and I have tried to solve by the following manner.
Basic
One can provide custom compare lambda function(since c++11) to the std::multiset of a member of a class as follows:
#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;
};
Simple enough.
My Situation
The member of Test class is
std::map<std::string, std::multiset<int, /* custom compare */>> scripts{};
I tried to use the std::multiset with custom
- functor
Compare(case - 1) std::greater<>(case - 2)- lambda function (case - 3)
The first two options are a success. But the case of lambda as a custom compare function it did not work. Here is the 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 message:
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
It sounds like I tried to default construct the passed lambda, which is not possible until c++20.
If that the case where has it happened? Is it possible to solve this using a lambda compare function within the scope of c++11 till c++17?
Yes. That exactly what happened here and due to the call of
std::map::operator[]at the line(s)Let's look into detail. The above call will result in a call of the following overload as the key being temporary
std::stringconstructed fromconst char*.Since C++17 this is equivalent to:
where the key_type(i.e. temporarly constructed
std::stringfromconst char*) should be move constructible, which happends fine.The mapped_type(i.e.
std::multiset<int, decltype(compare)>) should be default construct ed first and that requires the compare lambda should be also default constructed. From cppreference.com:That means, default construction of lambda closure type not available in C++17(that is what the compiler error is complaining about).
On the other hand, there is no captures are specified(i.e. stateless lambdas) in the
comparelambda there and hence it can be explicitly defaulted by the compilers which support C++20 standard.Not by using
std::map::operator[](as for the reason explained above), but Yes, the way what @JohnZwinck's has mentioned in his answer. I would like to explain, how that works.One of the constructors1 of
std::multisetprovides the possibility to pass the comparator object.The same time, the copy constructor and the move constructor for the lambda closure type have been defaulted since C++14. That means, if we have a possibility to provide the lambda as the first argument2(either by copying or by moving it), it would be the Basic case, what showed in the question.
Luckily, C++17 introduced the member function std::map::try_emplace
by which one can pass the lambda to the above-mentioned constructors1 of
std::multisetas the first argument2 like shown above. If we warp this into the member function of theTestclass, elements could be inserted to theCustomMultiList(i.e. values) of thescriptsmap.The solution would look like(same as the linked post, because I wrote that answer after I asking this question!)
(See Live)