Описание проблемы:
Рассмотрим некоторую структуру, имеющую член std::string name
. Для ясности пусть пусть a struct Human
, представляя информацию о людях. Помимо name
он может также содержать много других элементов данных.
Пусть есть контейнер std::vector<Human> vec
, где объекты уже отсортированы по name
. Также для ясности предположим, что все имена уникальны.
Проблема заключается в следующем: наличие некоторой строки nameToFind
выяснить, существует ли элемент в массиве с таким именем.
Решение и мой прогресс:
Очевидное и естественное решение, похоже, выполняет двоичный поиск с использованием функции std::binary_search
. Но есть проблема: тип искомого элемента (std::string
) отличается от типа элементов в контейнере (Human
), а std:: binary_search требует правила для сравнения этих элементов. Я попытался решить это тремя способами, описанными ниже. Первые два представлены только для иллюстрации эволюции моего решения и проблем, с которыми я столкнулся. Мой главный вопрос относится к третьему.
Попытка 1: конвертировать std::string
в Human
.
Напишите функцию сравнения:
bool compareHumansByNames( const Human& lhs, const Human& rhs )
{
return lhs.name < rhs.name;
}
Затем добавьте конструктор, который строит объект Human
из std::string
:
struct Human
{
Human( const std::string& s );
//... other methods
std::string name;
//... other members
};
и используйте binary_search в следующей форме:
std::binary_search( vec.begin(), vec.end(), nameToFind, compareHumansByNames );
Кажется, работает, но появляется две большие проблемы:
Во-первых, как инициализировать другие члены данных, но Human::name
, особенно в случае, когда у них нет конструктора по умолчанию? установка магических значений может привести к созданию объекта, который является семантически незаконным.
Во-вторых, мы должны объявить этот конструктор как не explicit
, чтобы разрешить неявные преобразования во время алгоритма. Плохие последствия этого хорошо известны.
Кроме того, такой временный объект Human
будет построен на каждой итерации, что может оказаться довольно дорогостоящим.
Попытка 2: конвертировать Human
в std::string
.
Мы можем попытаться добавить operator string ()
в класс Human
, который возвращает его name
, а затем использовать сравнение для двух std::string
s. Однако этот подход также неудобен по следующим причинам:
Во-первых, код не будет компилироваться сразу из-за обсуждаемой проблемы здесь. Нам придется немного поработать, чтобы компилятор использовал соответствующий operator <
.
Во-вторых, что означает "преобразовать человека в строку"? Существование такого преобразования может привести к семантически неправильному использованию класса Human
, что нежелательно.
Попытка 3: сравнение без конверсий.
Лучшим решением, которое я получил до сих пор, является создание
struct Comparator
{
bool operator() ( const Human& lhs, const std::string& rhs )
{
return lhs.name < rhs;
}
bool operator() ( const std::string& lhs, const Human& rhs )
{
return lhs < rhs.name;
}
};
и использовать двоичный поиск как
binary_search( vec.begin(), vec.end(), nameToFind, Comparator() );
Это компилируется и выполняется правильно, все выглядит нормально, но здесь начинается интересная часть:
Посмотрите http://www.sgi.com/tech/stl/binary_search.html. Здесь сказано, что тип значения ForwardIterator того же типа, что и T. ". Довольно запутанное ограничение, и мое последнее решение нарушает его. Посмотрите, что говорит об этом стандарт С++:
25.3.3.4 binary_search
template<class ForwardIterator, class T>
bool binary_search(ForwardIterator first, ForwardIterator last,
const T& value);
template<class ForwardIterator, class T, class Compare>
bool binary_search(ForwardIterator first, ForwardIterator last,
const T& value, Compare comp);
Требуется: Тип T является LessThanComparable (20.1.2).
В явном виде ничего не сказано о типе ForwardIterator
. Но в определении LessThanComparable, приведенном в 20.1.2, говорится о сравнении двух элементов того же типа. И вот что я не понимаю. Действительно ли это означает, что тип исследуемого объекта и тип объектов контейнера должны совпадать, и мое решение нарушает это ограничение? Или это не относится к случаю, когда используется компаратор comp
и относится только к случаю, когда по умолчанию используется operator <
для сравнения? В первом случае я смущен тем, как использовать std::binary_search
для решения этой проблемы, не сталкиваясь с проблемами, упомянутыми выше.
Заранее благодарим за помощь и находим время, чтобы прочитать мой вопрос.
Примечание. Я понимаю, что запись двоичного поиска вручную не требует времени и решит проблему мгновенно, но чтобы избежать повторного создания колеса, я хочу использовать std:: binary_search. Также мне очень интересно узнать о существовании такого ограничения в соответствии со стандартом.