Ответ
Класс или структура могут быть использованы в качестве ключа std::set, если для них определен строгий слабый порядок (Strict Weak Ordering). Это достигается одним из двух способов:
- Определение оператора
<для типа ключа. - Предоставление пользовательского функтора-компаратора в качестве второго шаблонного параметра
std::set.
Пример 1: Использование оператора <
struct Point {
int x, y;
// Определяем порядок: сначала по x, затем по y
bool operator<(const Point& other) const {
return std::tie(x, y) < std::tie(other.x, other.y);
}
};
std::set<Point> points; // Корректно, используется operator<
Пример 2: Использование пользовательского компаратора
struct Person {
std::string name;
int age;
};
// Компаратор, сравнивающий только по возрасту
struct CompareByAge {
bool operator()(const Person& a, const Person& b) const {
return a.age < b.age;
}
};
std::set<Person, CompareByAge> people;
// В этом set нельзя будет иметь двух людей с одинаковым возрастом.
Критические требования к компаратору (Compare):
- Антисимметричность: Если
comp(a, b) == true, тоcomp(b, a) == false. - Транзитивность: Если
comp(a, b) == trueиcomp(b, c) == true, тоcomp(a, c) == true. - Транзитивность эквивалентности: Если
!comp(a, b) && !comp(b, a)(эквивалентны) и!comp(b, c) && !comp(c, b), то!comp(a, c) && !comp(c, a).
Без выполнения этих условий поведение std::set не определено (UB).
Ответ 18+ 🔞
А, ну это же классика, ёпта! Смотри, тут всё просто, как два пальца обоссать, но есть свои подводные камни, блядь. Если хочешь свой кастомный тип в std::set пихнуть, то ему надо объяснить, как эти объекты друг с другом сравнивать. А то контейнер нихуя не поймёт, кто больше, кто меньше, и где ему этот элемент в своём сбалансированном дереве размещать.
Короче, есть два основных пути, как это провернуть.
Первый — самый простой. Просто нахуяриваешь для своей структуры оператор <. Сделал это — и всё, std::set уже доволен, он сам им пользоваться будет. Смотри, как это выглядит:
struct Point {
int x, y;
// Определяем порядок: сначала по x, затем по y
bool operator<(const Point& other) const {
return std::tie(x, y) < std::tie(other.x, other.y);
}
};
std::set<Point> points; // Корректно, используется operator<
Видишь? std::tie — это вообще гениальная штука, чтобы не писать эти ёбаные цепочки if (x != other.x) return x < other.x; else return y < other.y;. Красота, ядрёна вошь!
А второй путь — для хитрожопых ситуаций. Допустим, тебе не нужен стандартный порядок, или оператор < для твоего типа — это пиздец какая мудя. Тогда ты можешь дать set'у свою собственную функцию сравнения. Засовываешь её как второй шаблонный аргумент, и вуаля.
struct Person {
std::string name;
int age;
};
// Компаратор, сравнивающий только по возрасту
struct CompareByAge {
bool operator()(const Person& a, const Person& b) const {
return a.age < b.age;
}
};
std::set<Person, CompareByAge> people;
// В этом set нельзя будет иметь двух людей с одинаковым возрастом.
Смотри, какой прикол: в таком сете будут храниться уникальные значения возраста. Двух Вась с возрастом 30 быть не может, даже если у них имена разные. Потому что компаратору на имя вообще похуй, он смотрит только на age. Удивление пиздец, да?
Но вот тут, блядь, самое важное! К твоему компаратору, будь он оператором или отдельной структурой, есть жёсткие требования. Если их нарушить — получишь неопределённое поведение, и всё полетит к чёртовой матери в самый неподходящий момент. Запоминай, что он должен уметь:
- Не быть ебаньком-оборотнем: Если
comp(a, b)сказалtrue, тоcomp(b, a)обязан бытьfalse. Не может объект одновременно быть и меньше, и больше другого, это же ёперный театр. - Быть последовательным, как параноик: Если
aменьшеb, аbменьшеc, то иaобязан быть меньшеc. Без вариантов. - Иметь чёткое понятие "равенства": Если
aне меньшеbиbне меньшеa— значит, они для сета эквивалентны. И это отношение тоже должно быть транзитивным.
Нарушишь эти правила — и твой std::set превратится в тыкву, то есть в источник рандомных багов, которые хуй потом отловишь. Так что, чувак, думай головой, когда порядок определяешь.