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'} );
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(); });
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With