Это уже стало доброй традицией, что когда я еду в поезде или нахожусь в командировке, то вношу изменения в testlib. Видимо, причина тому — невозможность погрузиться в сложную задачу с большим контекстом по Polygon/Codeforces, но желание сделать что-то полезное здесь и сейчас.
Напоминаю, что testlib — мощная библиотека и стандард де-факто для подготовки задач по программированию, если вы используете С++. С помощью testlib можно значительно упростить подготовку: валидаторов, генераторов, чекеров и интеракторов. Все эти программы ниже буду называть testlib-программами.
Вот код ревизии, о которой идет речь. А вот ссылка с подсвеченными изменениями, которая может заменить весь текст поста. Как только будет понятно, что последние изменения финализированы, то будет выпущен новый релиз и обновим testlib в Полигоне. Выпущен релиз 0.9.4, который доступен со страницы загрузок.
Короткий список основных изменений:
Улучшена совместимость с компиляторами
В промежуточных версиях были сложности с компиляцией testlib-программ в Borland C++, некоторых версиях g++ и Microsoft Visual Studio C++. Всё это было исправлено, текущая версия компилируется в Borland C++ 5.6.4, g++ версий 0.7.2 и 0.8.1, Microsoft VS C++ версий 2005, 2008, 2010 и 2012.
Производительность
Сделаны некоторые улучшения производительности. Например, ранее ensure
(функция наподобие assert
, приводит к аварийному останову testlib-программы, если аргумент равен false
) приводил к созданию std::string даже, если вызов не приводил к аварийному останову.
Другое
- У InStream больше нет доступных конструктора копирования и оператора присваивания. Это запрещает передавать объекты InStream по значению, ведь такая передача является логической ошибкой.
- Добавлена функция
disableFinalizeGuard()
, чтобы отключить проверку вызова quitf в чекере и readEof в валидаторе перед выходом. Честно говоря, так и не понял зачем это надо было, но просили. Видимо, для каких-то хакерских целей из-за нетипичного использования testlib. - Добавлена функция
expectedButFound
для выхода с красивым сообщением. Вот примеры использования:expectedButFound(_wa, 5, 6)
— будет выход с _wa и сообщением типаexpected 5, but found 6
, еще примерexpectedButFound(_wa, 5, 6, "test case %d", 13)
завершит работу с _wa и сообщениемtest case 13: expected 5, but found 6
. Умеет сам красиво форматировать вещественные числа и сокращать сверхдлинные строки (добавлять в середине многоточие).
Создание InStream от строки
Теперь всю мощь InStream можно применять для произвольной строки. Т.е. вычитывать данные с помощью всяких readToken и проч. можно из произвольной строки. При создании InStream следует указать тот поток, от которого унаследуется поведение в случае ошибок. Например, InStream yesOrNo(inf, "YES")
. Может быть полезно в случае сложных разборов вывода (например, если удобно прочесть строку, что-то в ней проверить, а потом еще попарсить ее с помощью InStream).
Warnings
Неугомонный PavelKunyavskiy помог локализовать и закрыть пачку warnings в g++. В настоящее время testlib не порождает warnings даже при ключах -Wall -Wextra -Wconversion.
Опциональный новый вердикт: Unexpected EOF
Если скомпилировать с -DENABLE_UNEXPECTED_EOF, то при попытки прочитать что-то из потока произойдет вердикт unexpected eof
, если этого чего-то в потоке нет. Код выхода по умолчанию: 8. Полезно для интеракторов, так как иначе они могут путать некоторые вердикты.
Исправления ошибок
- выход с вердиктом о частичном решении класса 1, т.е. так:
quitf(_pc(1), "...message...")
; приводил к неправильному поведению; - генератор случайных чисел всегда оставлял один из битов нулевым (имеет значение только при генерации long long). Теперь рекомендуется явно указывать текущую версию генератора случайных чисел в генераторе вот так
registerGen(argc, argv, 1)
; - было некорректное поведение при чтении nan в качестве double на некоторых компиляторах;
- иногда неправильно обрабатывался дефис в паттернах для валидаторов/генераторов.
Вопрос: исследовались ли последствия от того, чтоб где-то в начале testlib.h написать
???
Это, кажется, улучшило бы кроссплатформенность для фрагментов наподобие
Сейчас подобные фрагменты компилируются в разных компиляторах C++ ну совсем по-разному из-за того, что одни раскрывают
max
функцией, а другие макроопределием (и с макроопределением#define max(a,b) (((a) > (b)) ? (a) : (b))
получается полная ересь, т.к.b
которое сравнивается иb
которое берётся как результат — разные). Как следствие, именно это выражение видаmax(1, ...)
может даже вернуть 0, блин!!! (например, в MS VC++ Express 2008 так реально происходит).Какое отношение имеет Testlib к функциям
min
иmax
?Если это сделать, не окажется ли, что нужно пропатчить ещё сотню-другую функций стандартной библиотеки на тех же основаниях?
А какой современный «компилятор» C++ использует макросы для
min
иmax
?Ну и ещё общее соображение. Вызывать в одной строке больше одной функции с побочными эффектами (как в вашем примере) и использовать при этом C++ мне кажется плохой идеей в принципе.
Даже если вы сами точно знаете, в каком порядке в соответствии с современным стандартом C++ (который, как известно, некоторым «компиляторам» не указ, ну да это другая история) всё выполнится, такой код сложнее читать другим. А если вы готовите задачу, довольно вероятно, что читать её исходники придётся ещё кому-то, когда что-то пойдёт не так.
(1-2) А почему, собственно, влазить во имя кроссплатформенности в генерацию случайных чисел и в чтение файлов можно, а в min / max нельзя? В чём великая и принципиальная разница?
Вот стОит ли добиваться, чтоб именно min / max стали кроссплатформенными — тут я возможно неправ. Как-то забыл во время написания стартового комментария о том, что вообще-то есть
__testlib_min
и__testlib_max
, определённые гарантированно как функции, и можно пользоваться ими. Но почему факт наличияrnd.next
versusrand()
надо выпячивать аж до compile error-ов на rand(), а об этой проблеме с min / max вообще никак не сообщать — всё равно непонятно.(3) Если не годится ранее приведённый пример MS VC++ Express 2008 — ну, в Microsoft Visual Studio Express 2012 for Windows Desktop то же самое. Или опять недостаточно современно?
(4) И при чём тут "больше одной функции с побочными эффектами "? Будет написано какое-нибудь
--- та же проблема будет ещё пожёстче. Или тут Вы тоже видите "больше одной"?
(1-2) А почему, собственно, влазить во имя кроссплатформенности в генерацию случайных чисел и в чтение файлов можно, а в min / max нельзя? В чём великая и принципиальная разница?
Да можно. Вопрос в том, где остановиться. Наверняка же это не единственные «сломанные» функции. Из недостатков подхода «починим всё» могу отметить, что уже при нынешнем 90-килобайтном testlib.h чекеры и валидаторы компилируются ненулевое время.
(3) Ну, печально, что в вижаке min и max не для людей сделаны. Не могу придумать современный use case для этого, кроме обратной совместимости.
А какая-нибудь версия Visual Studio реализует какой-нибудь стандарт C++?
(4) Это была подколка про ваш пример, в котором написано дословно следующее:
Какой порядок у двух вызовов rnd.next? Насколько я понимаю, он не специфицирован.
Здесь-то неважно, потому что умножение коммутативно (кстати, правда? или для трёх и более умножений вещественных чисел — уже нет?), но вот какая-нибудь похожая строка типа
имеет право вычислить координаты точки в произвольном порядке. Поэтому не надо так писать в генераторах.