Search code examples
c++templatesstlc++17template-argument-deduction

Can I specify comparator using std::set constructor without specifying all the template arguments


I want to construct a set with lambda comparator. Due to known limitations you can not specify lambda as template parameter(you need to decltype() it) so I thought about specifying the key of the map in the template argument list and comparator in the constructor argument. Something like:

std::set<Color> colors({ { "Red", 255, 0 , 0 }, { "Green", 0,255,0 },  { "Black", 0,0,0 } } , [](const Color& a, const Color& b){return a.name()<b.name();});

But from what I understand from error message as soon as I specified the template arguments(<Color>) I forced others to default(std::less for comparator). And the map constructor taking just comparator is not smart enough to get the Key type from comparator arguments, aka this does not work:

std::set colors([](const Color& a, const Color& b){return a.name()<b.name();});

Is there a way to specify I want a set of Colors, but let the comparator be specified by constructor.

Note that I can use constructor to deduce template types since C++17, but it is not pretty since I need to write a lot more than I want.

std::set colors(std::initializer_list<Color>{ { "Red", 255, 0 , 0 }, { "Green", 0,255,0 },  { "Black", 0,0,0 } } , [](const Color& a, const Color& b){return a.name()<b.name();}, std::allocator<char/*???*/>{});

full code here:


Solution

  • If I remember correctly, isn't possible with deduction guides (in C++17) explicit a template type and deduce the others.

    If you want deduce the type Color from the lambda comparator, the best I can imagine is the creation of a makeSetFromCmp() function

    template <typename Key>
    auto makeSetFromCmp (bool(*cmp)(Key const &, Key const &),
                         std::initializer_list<Key> const & il)
     { return std::set(il, cmp); }
    

    The trick is that passing first the comparator, the Key type can be deduced from the comparator so there is non need of explicit std::initializer_list<Key> calling the function.

    So you can write

    auto colors = makeSetFromCmp(+[](Color const & a, Color const & b)
                                       { return a.name() < b.name(); },
                                 { { "Red", 255, 0 , 0 },
                                   { "Green", 0,255,0 },
                                   { "Black", 0,0,0 } });
    

    Observe the + before the lambda definition: convert the lambda in a good-old function pointer.

    A little improved version of makeSetFromCmp() (with a third allocator template argument with a default value and with forwarding) could be

    template <typename Key, typename A = std::allocator<Key>>
    auto makeSetFromCmp (bool(*cmp)(Key const &, Key const &),
                         std::initializer_list<Key> && il,
                         A && all = A{})
     { return std::set(std::forward<std::initializer_list<Key>>(il),
                       cmp,
                       std::forward<A>(all)); }