Почему операторы сравнения std::vector и std::string определены как функции шаблона?

Небольшой обзор. Я пишу шаблон класса, который обеспечивает сильный typedef; сильным typedef Я противопоставляюсь регулярному typedef, который просто объявляет псевдоним. Чтобы дать представление:

using EmployeeId = StrongTypedef<int>;

Теперь существуют разные школы мысли о сильных typedefs и неявных преобразованиях. Одна из этих школ говорит: не каждый целое является EmployeeId, но каждый EmployeeId является целым числом, поэтому вы должны разрешать неявные преобразования из EmployeeId в integer. И вы можете реализовать это и написать такие вещи, как:

EmployeeId x(4);
assert(x == 4);

Это работает, потому что x получает неявное преобразование в целое число, а затем используется целочисленное сравнение равенства. Все идет нормально. Теперь я хочу сделать это с помощью вектора целых чисел:

using EmployeeScores = StrongTypedef<std::vector<int>>;

Итак, я могу делать такие вещи:

std::vector<int> v1{1,2};
EmployeeScores e(v1);
std::vector<int> v2(e); // implicit conversion
assert(v1 == v2);

Но я все еще не могу этого сделать:

assert(v1 == e);

Причина, по которой это не работает, объясняется тем, как std::vector определяет свою проверку равенства, в основном (по модулю стандартного):

template <class T, class A>
bool operator==(const vector<T,A> & v1, const vector<T,A> & v2) {
...
}

Это шаблон функции; потому что он отбрасывается на более ранней стадии поиска, он не позволит типу, который неявно преобразуется в вектор, который нужно сравнить.

Другой способ определения равенства будет таким:

template <class T, class A = std::allocator<T>>
class vector {
... // body

  friend bool operator==(const vector & v1, const vector & v2) {
  ...
}

} // end of class vector

В этом втором случае оператор равенства не является шаблоном функции, это просто регулярная функция, сгенерированная вместе с классом, аналогичная функции-члену. Это необычный случай, связанный с ключевым словом friend.

Вопрос (извините, фон был настолько длинным), почему не std::vector использует вторую форму вместо первой? Это делает vector более похожим на примитивные типы, и, как вы можете ясно видеть, это помогает в моем случае использования. Такое поведение еще более удивительно с string, так как легко забыть, что string является просто типедом шаблона класса.

Две вещи, которые я рассмотрел: во-первых, некоторые могут подумать, что, поскольку функция друга генерируется с классом, это приведет к жесткому сбою, если содержащийся тип вектора не поддерживает сравнение равенства. Это не тот случай; как функции-члены классов шаблонов, они не генерируются, если они не используются. Во-вторых, в более общем случае свободные функции имеют то преимущество, что их не нужно определять в том же заголовке, что и класс, который может иметь преимущества. Но это явно не используется здесь.

Итак, что дает? Есть ли веская причина для этого, или это был просто неоптимальный выбор?

Изменить: я написал быстрый пример, который демонстрирует две вещи: и то, что неявное преобразование работает по желанию с подходом друга, и что никаких жестких сбоев не возникает, если шаблонный тип не отвечает требованиям оператора равенства (очевидно, предполагая, что в этом случае оператор равенства не используется). Изменить: улучшено, чтобы контрастировать с первым подходом: http://coliru.stacked-crooked.com/a/6f8910945f4ed346.

Ответы

Ответ 1

Техника, которую вы описываете (то, что я называю операторами Кенига), не была известна, по крайней мере, не широко, в точке vector была разработана и изначально указана.

Теперь его изменение потребует большей осторожности, чем использование его изначально, и более обоснования.

Как можно предположить, сегодня операторы Koenig будут использоваться вместо операторов шаблонов.

Ответ 2

EDIT: после того, как я перечитаю свое объяснение и под влиянием нескольких комментариев, я убежден, что мои оригинальные рассуждения не являются действительно убедительными. Мой ответ по существу попытался утверждать, что хотя значение x может быть неявно преобразовано в значение y другого типа, "автоматическое" сравнение равенства между ними может не обязательно ожидаться. Для контекстуализации я все еще оставляю здесь код, который я использовал в качестве примера.

struct B {};

template <class T>
struct A {
  A() {}
  A(B) {}
  friend bool operator==(const A<T>&, const A<T>&) { return false; }
};

// The template version wouldn't allow this to happen.
// template <class T>
// bool operator==(const A<T>&, const A<T>&) { return false; }

int main() {
  A<B> x;
  B y;
  if (x == y) {} //compiles fine
  return 0;
}