8

Lets say I have a vector of strings and I want to find all strings, that start with 'a', so I can do this:

struct cmp {
    bool operator()( const std::string &s, char c ) const { return s.front() < c; }
    bool operator()( char c, const std::string &s ) const { return s.front() < c; }
};
std::vector<std::string> strings;
...
std::sort( strings.begin(), strings.end() );
auto range = std::equal_range( strings.begin(), strings.end(), 'a', cmp{} );
...

This method is error prone, as it is easy to make mistake (for example I think it should be c < s.front() in second method) and has code duplication.

So is it possible to implement comparison function with generic lambda instead of structure with 2 methods?

More generic question, why value to compare has to be passed as argument to std::lower_bound, std::upper_bound and std::equal_range when it can easily be captured by lambda or passed to comparison structure, and then this issue will not be there at all?

How it could work if std::equal_range would not require value?

struct cmp {
    cmp( char lc ) : c( lc ) {}
    bool operator()( const std::string &s ) const { return s.front() < c; }
    char c;
};
std::vector<std::string> strings;
...
std::sort( strings.begin(), strings.end() );
auto range = std::equal_range( strings.begin(), strings.end(), cmp{'a'} );
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
Slava
  • 43,454
  • 1
  • 47
  • 90
  • If you are willing to pass `"a"` in place of `'a'`, then a lambda taking two strings and comparing their first characters would work. Otherwise, a named class with two overloads looks like the cleanest solution to me. Note though that the second overload, as currently written, does its comparison the wrong way round. – Igor Tandetnik Oct 30 '15 at 04:29
  • @IgorTandetnik that's obvious, but it could be not string, but an object, which sorted by part of it, so I do not want to create whole object just for use it as a key. – Slava Oct 30 '15 at 04:32
  • 1
    You can use `std::partition_point`. – T.C. Oct 30 '15 at 05:19
  • @T.C. can you provide an example as answer? thanks – Slava Oct 30 '15 at 13:07

2 Answers2

5

lower_bound is

std::partition_point(strings.begin(), strings.end(),
                     [](const auto& s) { return s.front() < 'a'; });

upper_bound is

std::partition_point(strings.begin(), strings.end(),
                     [](const auto& s) { return s.front() <= 'a'; });

Yes, that means you have to write two calls to get the equivalent of equal_range. You can wrap this into a free template:

template<class Iter, class T, class Proj>
std::pair<Iter, Iter> my_equal_range(Iter first, Iter last, const T& value, Proj proj) {
    auto b = std::partition_point(first, last, [&](const auto& s) { return proj(s) < value; });
    auto e = std::partition_point(b, last, [&](const auto& s) { return !(value < proj(s)); });
    return {b, e};
}

And call it as

my_equal_range(strings.begin(), strings.end(), 'a',
               [](const auto& s) { return s.front(); });

The Ranges TS working draft adds projections to algorithms, so you will (eventually) be able to do this:

std::experimental::ranges::equal_range(strings, 'a', {},
                                       [](const auto& s) { return s.front(); });
T.C.
  • 133,968
  • 17
  • 288
  • 421
4

To answer your first question whether the comparator can be implemented using a generic lambda, yes, it can. You'll also need a couple of helper functions to return the desired result given a char or string argument.

auto get_char(char c)               { return c; }
auto get_char(std::string const& s) { return s.front(); }
auto cmp = [](auto const& l, auto const& r) { return get_char(l) < get_char(r); };

Live demo


One reason you cannot simply have the comparator capture the value is because the two overloads of equal_range could then be ambiguous, you'd need a slightly different name or some other way (for instance, a tag argument) to disambiguate the two.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Good solution, but not perfect :( It has the same issue that stopped me from using algorithms before lambda - you have to write functors outside of scope of code where it is used. – Slava Oct 30 '15 at 13:52