Допустим, у меня есть вектор строк, и я хочу найти все строки, которые начинаются с 'a', поэтому я могу сделать это:

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{} );
...

Этот метод подвержен ошибкам, поскольку в нем легко сделать ошибку (например, я думаю, что во втором методе должно быть c < s.front()) и имеет дублирование кода.

Так можно ли реализовать функцию сравнения с общей лямбдой вместо структуры с двумя методами?

Более общий вопрос: почему значение для сравнения должно передаваться в качестве аргумента в std::lower_bound, std::upper_bound и std::equal_range, когда оно может быть легко захвачено лямбда-выражением или передано в структуру сравнения, а затем эта проблема там вообще не будет?

Как это могло бы работать, если бы std::equal_range не требовало значения?

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'} );
8
Slava 30 Окт 2015 в 07:26

2 ответа

Лучший ответ

lower_bound - это

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

upper_bound - это

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

Да, это означает, что вам нужно написать два вызова, чтобы получить эквивалент equal_range. Вы можете обернуть это в бесплатный шаблон:

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};
}

И назовите это как

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

Рабочий проект Ranges TS добавляет прогнозы к алгоритмам, так что вы (в конечном итоге) сможете это сделать:

std::experimental::ranges::equal_range(strings, 'a', {},
                                       [](const auto& s) { return s.front(); });
4
T.C. 30 Дек 2017 в 14:15

Чтобы ответить на ваш первый вопрос, можно ли реализовать компаратор с использованием общей лямбды, да, это возможно. Вам также понадобится пара вспомогательных функций, чтобы вернуть желаемый результат с помощью аргумента char или string.

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 демо


Одна из причин, по которой компаратор не может просто захватить значение, заключается в том, что две перегрузки {{X0} } может быть неоднозначным, вам понадобится немного другое имя или какой-то другой способ (например, аргумент тега) для устранения неоднозначности.

4
Praetorian 30 Окт 2015 в 06:50