14

One of the places I hoped I would be able to use the new template argument deduction, was in construction of std::set's / std::maps / any other containers with custom comparators - my goal is to create a one-line statement, that will create an efficient set with a lambda comparator. What I can do since C++11 is:

std::set<int, std::function<bool(int, int)>> s([](int a, int b) {return a > b;});

But since it uses std::function, it is noticeably slower.

Another option is:

auto mycomp = [](int a, int b) {return a > b; };
std::set<int, decltype(mycomp)> s(mycomp);

It gets the job done, but 1) It requires 2 lines, and creation of the mycomp variable 2) I need to pass mycomp's type explicitly.

As I read on reference pages, none of the standard containers has a deduction guide for this kind of situation. Unfortunately, I'm afraid it cannot be even done with the current language standard (C++17), as one can find:

Class template argument deduction is only performed if no template argument list is present. If a template argument list is specified, deduction does not take place.

What is the reasoning behind this? Why didn't they allow partial argument deduction? I guess there are some problems with it that I overlook, but in my opinion it would be more than helpful.

max66
  • 65,235
  • 10
  • 71
  • 111
Kaznov
  • 1,035
  • 10
  • 17
  • 1
    Partial deduction was in proposal first, but has been removed. See [c17-class-template-partial-deduction](https://stackoverflow.com/questions/41833630/c17-class-template-partial-deduction) – Jarod42 May 28 '18 at 11:45
  • @Kaznov: "*my goal is to create one-line statement, that will create efficient set with lambda comparator.*" ... why is that your goal? What's wrong with just creating a struct with an `operator()` overload? Why do you *have to* (or even *want to*) use lambdas? – Nicol Bolas May 28 '18 at 13:16

4 Answers4

11

As alternative, you can still make the old make_xxx

template <typename T, typename COMP>
std::set<T, COMP> make_set(COMP comp)
{
    return std::set<T, COMP>{comp};
}


auto s = make_set<int>([](int a, int b) {return a > b; });
Jarod42
  • 203,559
  • 14
  • 181
  • 302
11

In one line (C++17)

std::set s(std::initializer_list<int>{}, [](int a, int b) {return a > b; });
max66
  • 65,235
  • 10
  • 71
  • 111
  • Thanks! I wasn't aware of this constructor existance. Well, it solves the problem with containers. – Kaznov May 28 '18 at 11:48
  • 10
    @Kaznov - works but is not really elegant: that `std::initializer_list{}`, just to explicit the `int` type, is very ugly. – max66 May 28 '18 at 11:59
5

What you should be doing is writing a comparator functor type, so you don't need to proxy to it when using your set. It will be a lot more lines of code (the atrocity!), but is better in virtually every way:

struct MyComparator
{
    bool operator()(int a, int b) const { ...; }
};
using MySet = std::set<int, MyComparator>;

And from then on, you'd just create your customly sorted set where you need it.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • I know this is the best way of solving this problem, but I was looking for the most compact one. – Kaznov May 28 '18 at 11:55
  • 1
    @Kaznov: Why does it need to be compact? Having a big lambda in the middle of a template specialization doesn't make your code easier to read. This less "compact" is far easier to read than the lambda. – Nicol Bolas May 28 '18 at 13:17
  • I was looking for the fastest way of doing this, to use it on algorithmic competition. There you don't care how the code looks like, the shorter the better. – Kaznov May 28 '18 at 15:28
0

Because of ambiguity it brings when template parameter with default value are present. Also, in case of variadic templates.

code707
  • 1,663
  • 1
  • 8
  • 20