Почему операторы сравнения 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;
}