Зачем использовать std::less в качестве функтора по умолчанию для сравнения ключей в std::map и std:: set?

мне интересно, почему std::map и std::set использовать std::less в качестве функтора по умолчанию для сравнения ключей. Почему бы не использовать функтор, который работает аналогично strcmp? Что-то вроде:

  template <typename T> struct compare
  {
     // Return less than 0 if lhs < rhs
     // Return 0 if lhs == rhs
     // Return greater than 0 if lhs > rhs
     int operator()(T const& lhs, T const& rhs)
     {
        return (lhs-rhs);
     }
  }

сказать map есть два объекта в нем, с помощью клавиш key1 и key2. Теперь мы хотим вставить другой объект с ключом key3.

при использовании std::less на insert функция должна сначала вызвать std::less::operator() С key1 и key3. Предположим std::less::operator()(key1, key3) возвращает false. Он должен позвонить std::less::operator() снова с ключами поменялись, std::less::operator()(key3, key1) С key1 равна key3 или key3 больше key1. Есть два вызова std::less::operator() принять решение, если первый вызов возвращает значение false.

Had std::map::insert используется compare, там будет достаточно информации, чтобы принять правильное решение, используя только один вызов.

в зависимости от типа ключа в карте, std::less::operator()(key1, key2) может быть дорогим.

если я не упускаю что-то очень простой, не должен std::map и std::set использовать что-то вроде compare вместо std::less как функтор по умолчанию для сравнения ключей?

2 ответов


Я решил спросить об этом Александра Степанова (дизайнера STL). Я могу процитировать его следующим образом:--2-->

Первоначально я предложил 3-сторонние сравнения. Стандартный комитет просил мне нужно перейти на стандартные операторы сравнения. Я сделал то, что мне сказали. Я выступал за добавление 3-полосных компонентов к стандарту для в течение 20 лет.

но обратите внимание, что, возможно, непреднамеренно, 2-way не является огромными накладными расходами. вы не должны сделайте вдвое больше сравнений. это только одно сравнение на узел на пути вниз (без проверки равенства). Стоимость не может вернуться раньше (когда ключ находится в не-листе) и одно дополнительное сравнение в конце (замена аргументов для проверки равенства). Если я не ошибаюсь, это делает

1 + 1/2*1 + 1/4*2 + 1/8*3 + ...
= 1 + 1/2+1/4+1/8+... + 1/4+1/8+... + ...
-> 3  (depth -> infty)

дополнительные сравнения в среднем по сбалансированному дереву, содержащему запрашиваемый элемент.

С другой стороны, 3-стороннее сравнение не имеет ужасных накладных расходов: Branchless 3-полосное сравнение целых чисел. Теперь, является ли дополнительная ветвь для проверки результата сравнения против 0 (равенства) на каждом узле меньше накладных расходов, чем оплата ~3 дополнительных сравнения в конце, это другой вопрос. Возможно, это не имеет большого значения. Но я думаю, что само сравнение должно было быть 3-значным, чтобы решение о том, использовать ли все 3 результата, могло быть изменено.

Update: см. комментарии ниже, почему я думаю, что 3-стороннее сравнение лучше в деревьях, но не обязательно в плоских массивах.


контейнеры основанные деревом только требуют строгий слабый полный приказывать.

см.https://www.sgi.com/tech/stl/StrictWeakOrdering.html

  1. запись

    точка вставки для карт и множеств определяется исключительно одним двоичным поиском, например lower_bound или upper_bound. Сложность выполнения двоичного поиска -O(log n)

  2. чтение

    то же самое относится к поиску: в поиск значительно эффективнее, чем сканирование линейного равенства, именно потому, что большинство элементов не нужно сравнивать. Фокус в том, что контейнеры заказаны.


вывод:equality информация не должна присутствовать. Просто, что элементы могут иметь соответствующий заказ.

на практике это просто означает, меньше ограничений на типы элементов, меньше работы для реализации требования и оптимальная производительность в общих сценариях использования. Всегда будут компромиссы. (Например. для больших коллекций хэш-таблицы (ненумерованный наборы и карты) часто более эффективны. Обратите внимание, что эти do требуются equatable элементы, и они используют схему хэширования для быстрого поиска)