Жила-была библиотека у меня на поддержке. Всё у неё было хорошо: собиралась под Linux, работала, не падала. Однажды пришли люди с просьбой (требованием) собрать её под Windows. Почему бы и нет? Но с первого раза не получилось. Корнем зла оказалась рукописная криптография, которая в какой-то момент умножала два 64-битных целых числа. Для сохранения результата такого умножения потребуется число на 128 бит, и в библиотеке использовался тип __int128. Он прекрасен: имеет естественный интерфейс, поддерживается несколькими компиляторами (gcc, clang), работает без аллокации памяти, но главное — он есть.
Разработчики компилятора из Microsoft поддержку этого типа не обеспечили, аналогов не придумали — или я их не нашёл. Единственное пришедшее на ум кроссплатформенное решение — Big Numbers из OpenSSL, но оно несколько другое. В итоге конкретно эту проблему я решил «велосипедом»: нужен был только uint128_t с ограниченным набором операций. Из нескольких чужих решений собрал класс UInt128, положил его в исходники библиотеки. «Велосипед» — как раз и есть та самая боль. Задача была решена.
Вечером того же дня пошёл развеяться на мероприятие, где люди из «Рабочей Группы 21» (РГ21) рассказывали о том, как они обрабатывают напильником С++. Я послушал и написал на cpp-proposal@yandex-team.ru короткое письмо из двух предложений на тему «нужен int128 в сpp». Антон Полухин в ответ поведал о том, что разработчики стандарта хотят решить эту проблему раз и навсегда. Логично: сейчас мне потребовалось число на 128 бит, а кому-то надо работать с числами на 512 бит — и этот кто-то тоже захочет удобный инстумент.
Ещё Антон поведал, что есть два пути к решению: через ядро языка и через библиотеку. Существует мнение, и я его разделяю, что синтаксис языка и так достаточно сложен: добавить в язык конструкцию, которая обеспечит кроссплатформенную и эффективную возможность использовать числа разной точности, будет очень непросто. А вот в рамках библиотеки справиться вполне реально: шаблоны — наше всё. «Нужен работающий прототип, — сказал Антон. — И желательно с тестами». А ещё выяснилось, что тип должен быть plain old data (POD), чтобы понравиться большему количеству людей.
И я пошёл делать прототип. Название wide_int выбрал осознанно: устойчивых ассоциаций с таким названием нет, во всяком случае — распространённых. Например, big_number мог ввести в заблуждение — мол, он хранит значение в куче (heap) и никогда не переполняется. Хотелось получить тип с поведением, аналогичным поведению фундаментальных типов. Хотелось сделать тип, размер которого будет продолжать их прогрессию: 8, 16, 32, 64… 128, 256, 512 и т. д. Через какое-то время появился работающий прототип. Сделать его оказалось несложно: он должен был компилироваться и работать, но необязательно по-настоящему эффективно и быстро.
Антон его изучил, сделал ряд замечаний. Например, не хватало преобразования к числам с плавающей точкой, надо было пометить максимальное число методов как constexpr и noexcept. От идеи так ограничивать выбор размера числа Антон меня отговорил: сделал размер, кратный 64. После этого мы совместно с Антоном написали текст самого предложения. Оказалось, что писать документ гораздо сложнее, чем писать код. Ещё немного шлифовки — и Антон (как единственный понимающий, что делать дальше) начал показывать наше предложение людям из комиссии по стандартизации.
Критиковали немного. Например, кто-то высказал желание сделать целочисленный тип, который не переполняется. Или тип, размер которого можно задать с точностью до бита (и получить, например, размер в 719 бит!). Предложение отказаться от привязки к количеству бит, а задавать количество машинных слов, мне показалось самым странным: бизнес-логике всё равно, сколько слов в числе на какой-то платформе, — ей важно однозначно определять одни и те же числа на разных платформах. Скажем, уникальный идентификатор пользователя — беззнаковое целое число из 64 бит, а не из одного unsigned long long.
Если в двух словах, это были идеи для других типов. Критику мы выслушали, каких-то существенных замечаний не оказалось. После этого Антон опубликовал предложение к стандарту и поехал его защищать на очередное заседание комиссии.
Защита прошла успешно: наше предложение взяли в работу — другими словами, оно будет рассматриваться на заседаниях и дальше. Был высказан ряд замечаний; сейчас мы вносим нужные исправления. В частности, комиссия всё-таки попросила в wide_int оперировать количеством машинных слов. Аргументация проста: тип так или иначе будет реализован, но если использовать эти самые машинные слова, то выйдет эффективнее. У меня остаётся надежда, что удобный алиас uint128_t попадёт в стандарт — тогда я смогу выкинуть свой тип UInt128, пока его не увидел кто-то ещё. =)
Актуальную версию имплементации можно найти здесь. Ещё есть документ и небольшое обсуждение на stdcpp.ru. Всего со дня отправки первого письма на cpp-proposal@yandex-team.ru прошло около четырёх месяцев. Из них около 40 часов нерабочего времени мною было потрачено на это предложение. На момент написания статьи имплементация распухла на 1622 строки, да ещё тесты добавили 1940 строк.
Считаю важным рассказать, что мне всё это даёт. Во-первых, интересно узнать, как строится процесс принятия решений и что важно при проектировании интерфейсов в стандартной библиотеке.
Во-вторых, я могу изменить C++ в ту сторону, которая мне нравится. Конечно, тут важно помнить, что для реализации любой крупной идеи нужны единомышленники. Например, есть идея сделать интерфейс для контейнеров и строк чуть более выразительным и очевидным: я хотел добавить контейнерам operator bool(). Но неравнодушные к C++ коллеги дали понять, что я неправ.
В-третьих, я много нового для себя узнал о шаблонах в С++.
В-четвёртых, говорят, что это как-то усилит моё резюме… Пока не проверял, но поверю опытным коллегам на слово.
В-пятых, когда Бьярне Страуструп где-то в переписке, посвящённой обсуждению твоей работы, пишет кому-то «+1» — это весело. =) Даже если он поддерживает чью-нибудь критику.
Напоследок скажу, что про новости и мероприятия РГ21 С++ можно узнавать, подписавшись в Твиттере на канал stdcppru.
Комментарии (0)