22

Trying to override map::compare function using lambda, it seems that the following solution works.

auto cmp = [](const int&a, const int& b) { return a < b; };
std::map<int, int, decltype(cmp)> myMap(cmp);

But, I had to define cmp first and use it later.
Can I do this without defining 'cmp'?

MBZ
  • 26,084
  • 47
  • 114
  • 191

4 Answers4

26

No, you can't use lambda in unevaluated context -- i.e. template parameters as in your example. So you must define it somewhere else (using auto) and then use decltype... the other way, as it was mentioned already is to use an "ordinal" functors

If your question is about "how to use lambda expression *once* when define a map" you can exploit implicit conversion of lambdas to std::function like this:

#include <iostream>
#include <functional>
#include <map>

int main()
{
    auto m = std::map<int, int, std::function<bool(const int&, const int&)>>{
        [](const int& a, const int& b)
        {
            return a < b;
        }
    };
    return 0;
}

you may introduce an alias for that map type to reduce typing later...

zaufi
  • 6,811
  • 26
  • 34
8
#include <iostream>
#include <functional>
#include <map>
#include <typeinfo>

typedef std::map< int, int, std::function<bool(const int&, const int&)> > MyMap;

int main()
{
    auto cmp = [](const int& a, const int& b) { return a < b; };
    MyMap map(cmp);

    return 0;
}

Using std::function to provide the appropriate type signature for the comparator type you can define your map type and then assign any lambda compare you wish to.

Borgleader
  • 15,826
  • 5
  • 46
  • 62
  • 4
    This is what the OP is asking for, but really, introducing `std::function` feels like a bad solution. How about an alias template instead? `template using MyMap = std::map;` and then you don't have to define the comparator until you instantiate the map. – Praetorian Aug 04 '13 at 17:30
  • @Praetorian I'm stuck with an inferior compiler (VS2012) which doesn't support template aliases so I have no experience with them. – Borgleader Aug 04 '13 at 17:31
  • template aliases wouldn't help anyway: __you can't use lambda in unevaluated context__, but you have to specify a `Comp` parameter anyway (at instantiation point). – zaufi Aug 04 '13 at 17:42
  • but you can use std::function<> in a template alias. see my post for example. I actually think std::function ins't a bad solution if there is a compelling reason to handle multiple maps with many different comparators especially if some capture a variable (in which case an arbitrary number of unique comparisons might exist). – Jake Aug 04 '13 at 18:14
5

You could do something like this where the type of the map is deduced from the function you pass to a function.

#include <map>

template<class Key, class Value, class F>
std::map<Key, Value, F> make_map(const F& f) {
    return std::map<Key, Value, F>{f};
}

int main() {
    auto my_map = make_map<int, int>([](const int&a, const int& b) { return a < b; });
    my_map[10] = 20;
}

I don't see a ton of reason for doing this but I wont say it's useless. Generally you want a known comparator so that the map can be passed around easily. With the setup above you are reduced to using template functions all the time like the following

tempalte<class F>
void do_somthing(const std::map<int, int, F>& m) {

}

This isn't necessarily bad but my instincts tell me that having a type which can ONLY be dealt with by generic functions is bad. I think it works out fine for lambda functions but that's about it. The solution here is to use std::function

#include <map>
#include <functional>

template<class Key, class Value>
using my_map_t = std::map<Key, Value, std::function<bool(const Key&, const Key&)>>;

int main() {
    my_map_t<int,  int> my_map{[](const int&a, const int& b) { return a < b; }};
    my_map[10] = 20;
}

Now you can use any predicate you want and you have a concrete type to work with, my_map

hope this helps!

Jake
  • 747
  • 1
  • 5
  • 19
1

In C++20 you can do this:

std::map<int, int, decltype([](const int&a, const int& b) { return a < b; })> myMap;


int main() {
    myMap.insert({7, 1});
    myMap.insert({46, 2});
    myMap.insert({56, 3});
    for (const auto& [key,value]:myMap) {
        std::cout <<  key <<  "  " << value << std::endl;
    }
}
NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277