Ответ
Требования зависят от типа контейнера:
1. Для упорядоченных контейнеров (std::map, std::set, std::multimap, std::multiset):
Ключ должен поддерживать строгое слабое упорядочение. На практике это означает, что для типа ключа должен быть определен оператор сравнения operator< или предоставлен пользовательский функтор-компаратор.
// Пример ключа для std::set или std::map
struct Point {
int x, y;
// Определяем оператор < для строгого слабого упорядочения
bool operator<(const Point& other) const {
// Используем std::tie для лексикографического сравнения
return std::tie(x, y) < std::tie(other.x, other.y);
}
};
// Использование
std::set<Point> pointSet;
std::map<Point, std::string> pointMap;
2. Для неупорядоченных контейнеров (std::unordered_map, std::unordered_set):
Ключ должен быть хешируемым и сравнимым на равенство.
- Для типа должен быть специализирован
std::hash. - Должен быть определен оператор
operator==или предоставлен предикат равенства.
// Пример ключа для std::unordered_set или std::unordered_map
struct Point {
int x, y;
// Определяем оператор ==
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
// Специализируем std::hash для Point
namespace std {
template<> struct hash<Point> {
size_t operator()(const Point& p) const noexcept {
// Комбинируем хэши полей. Используем XOR и сдвиг для лучшего распределения.
return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
}
};
}
// Использование
std::unordered_set<Point> pointUSet;
std::unordered_map<Point, int> pointUMap;
Важно: Для пользовательских типов в качестве ключа в std::unordered_* контейнерах часто также требуется определить operator== в том же пространстве имен, что и сам тип (или в std), чтобы ADL мог его найти.
Ответ 18+ 🔞
А, ну это же классика, ёпта! Сидишь такой, пишешь код, и тут — бац! — надо свою структуру в std::map или unordered_set засунуть. А оно не компилируется, потому что компилятор — пидарас шерстяной — не понимает, как твои кастомные объекты сравнивать или хешировать. Давай разберём, чтобы не было волнения ебать.
Смотри, тут всё зависит от того, в какой контейнер ты лезешь.
1. Для контейнеров, которые всё держат по порядку (map, set, и их многовариантные братья)
Тут ключ должен уметь сравниваться. По-умному это называется «строгое слабое упорядочение». Короче, для твоего типа должен быть operator< или свой компаратор, который объяснит, какой объект «меньше» другого. Без этого — нихуя не получится.
// Допустим, есть точка
struct Point {
int x, y;
// Вот тут мы говорим компилятору: "Э, бошка, думай! Вот как сравнивать"
bool operator<(const Point& other) const {
// Используем std::tie, чтобы не выёбываться с if-ами
return std::tie(x, y) < std::tie(other.x, other.y);
}
};
// И теперь можно юзать
std::set<Point> pointSet; // Всё, работает
std::map<Point, std::string> pointMap; // Тоже ок
Если operator< не написать, компилятор посмотрит на тебя как на идиота и скажет что-то вроде «не могу сравнить, иди на хуй». Честно.
2. Для неупорядоченных контейнеров (unordered_map, unordered_set)
А вот тут уже цирк, ёперный театр! Порядок не важен, но зато ключ должен быть хешируемым и сравнимым на равенство. То есть:
- Нужна специализация
std::hashдля твоего типа — чтобы можно было получить числовой хеш. - Нужен
operator==— чтобы понять, а не один и тот же ли это объект, если хеши совпали (коллизии же бывают, овердохуища).
struct Point {
int x, y;
// Сперва учим сравнивать на равенство
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
// А теперь самое интересное — учим STL хешировать нашу точку
namespace std {
template<> struct hash<Point> {
size_t operator()(const Point& p) const noexcept {
// Берём хеши от полей и как-то их смешиваем. Часто через XOR.
// Сдвиг тут чтобы для точек (1,0) и (0,1) хеши были разные, а то будет манда с ушами.
return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
}
};
}
// И теперь можно!
std::unordered_set<Point> pointUSet;
std::unordered_map<Point, int> pointUMap;
Важный момент, на котором многие обжигаются: для unordered_* контейнеров operator== должен быть виден в нужном пространстве имён (обычно рядом с типом или в std), иначе ADL его не найдёт и будет тихий ужас. Если не сделать — будет тебе хиросима и нигерсраки в лучшем случае, или просто не скомпилируется.
Короче, запомни: для порядка — operator<, для беспорядка — hash + operator==. Всё остальное — от лукавого.