Некоторое время назад я сам наткнулся на грабли при использовании библиотеки Folly. Мне потребовался std::hash для std::pair<int, some_my_type>, при этом std::hash для some_my_type у меня уже был. Поэтому я сделал специализацию std::hash для std::pair<int, some_my_type>, в которой использовал std::hash<some_my_type>. Собрал код, стал проверять и выяснил, что на некоторых тестовых сценариях происходит что-то странное. Начал разбираться и внезапно обнаружил, что мой std::hash для std::pair<int, some_my_type> не вызывается вообще.
Дело оказалось в том, что разработчики Folly подложили свинью своим пользователям и определили специализацию std::hash<std::pair<T, U>>. Чего делать по стандарту нельзя (за цитату из стандарта большое спасибо Константину Шарону):
• [namespace.std] (C++23 draft, §16.4.2.2/1):
The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std, unless otherwise specified.
• The only allowed exception is that you may specialize certain templates in std for user-defined types.
[unord.hash] (C++23 draft, §24.5.4.2/1): For all object types Key for which there exists a specialization std::hash, the program may define additional specializations of std::hash for user-defined types.
Тут особый упор нужно сделать на "for user-defined types", тогда как std::pair<T, U> -- это не user-defined type. Вот std::pair<int, some_my_type> -- это уже user-defined type и для него специализацию std::hash делать можно.
Об этом несколько месяцев назад я писал в LinkedIn. Но недавно на Reddit-е обнаружил ссылку на статью Simplify hashing in C++ в которой, ни много, ни мало, предлагается делать тоже самое, т.е. определять специализации std::hash для std::pair<T, U>.
Поскольку такое заблуждение существует, то приходится заострять на этом внимание. Поэтому, пожалуйста, не делайте собственные специализации std::hash<std::pair<T, U>>.
А если вы сомневаетесь в правильности того, о чем я говорю, то представьте себе ситуацию:
- вы разрабатываете программу и определили какой-то собственный тип your_type;
- вы захотели использовать your_type в качестве ключа в unordered_map и сделали для your_type специализацию std::hash;
- затем вы захотели сэкономить себе время и силы и сделали специализацию std::hash для std::pair<T, U>. Теперь у вас автоматически поддерживается хеширование и для std::pair<int, your_type>, и для std::pair<your_type, int>, и прочих сочетаний your_type с другими типами, поддерживающими кэширование;
- где-то в программе вы используете std::unordered_set<std::pair<int, your_type>>
- все у вас работает нормально;
- затем в один прекрасный момент вы подключаете в проект стороннюю библиотеку, в которую отдаете your_type, и которая использует std::pair<int, your_type> в качестве ключа в Folly::F14FastMap;
- и тут оказывается, что у вас в проекте есть два определения для std::hash<std::pair<int, your_type>>
Поздравляю, у вас в коде Undefined Behaviour из-за нарушения one definition rule.
Реально хотите собственными руками подсаживать UB в код?
Разработчиков Folly понять можно: они делали Folly для своих собственных нужд и в их программах, разработанных под задачи Facebook-а, вряд ли могут встретиться другие специализации для std::hash. А то, что Folly применяют и за пределами Facebook-а, в том числе смешивая в одном проекте Folly с кучей других библиотек -- это не проблемы разработчиков Folly.
Комментариев нет:
Отправить комментарий