Блог пользователя MikeMirzayanov

Автор MikeMirzayanov, 11 лет назад, По-русски

Это уже стало доброй традицией, что когда я еду в поезде или нахожусь в командировке, то вношу изменения в 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 на некоторых компиляторах;
  • иногда неправильно обрабатывался дефис в паттернах для валидаторов/генераторов.
  • Проголосовать: нравится
  • +43
  • Проголосовать: не нравится

»
10 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится

Вопрос: исследовались ли последствия от того, чтоб где-то в начале testlib.h написать

#ifndef NOMINMAX
#define NOMINMAX
#endif

???

Это, кажется, улучшило бы кроссплатформенность для фрагментов наподобие

return max(1, (int)(10*rnd.next(1.0)*rnd.next(1.0)));

Сейчас подобные фрагменты компилируются в разных компиляторах C++ ну совсем по-разному из-за того, что одни раскрывают max функцией, а другие макроопределием (и с макроопределением #define max(a,b) (((a) > (b)) ? (a) : (b)) получается полная ересь, т.к. b которое сравнивается и b которое берётся как результат — разные). Как следствие, именно это выражение вида max(1, ...) может даже вернуть 0, блин!!! (например, в MS VC++ Express 2008 так реально происходит).

  • »
    »
    10 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    1. Какое отношение имеет Testlib к функциям min и max?

    2. Если это сделать, не окажется ли, что нужно пропатчить ещё сотню-другую функций стандартной библиотеки на тех же основаниях?

    3. А какой современный «компилятор» C++ использует макросы для min и max?

    Ну и ещё общее соображение. Вызывать в одной строке больше одной функции с побочными эффектами (как в вашем примере) и использовать при этом C++ мне кажется плохой идеей в принципе.
    Даже если вы сами точно знаете, в каком порядке в соответствии с современным стандартом C++ (который, как известно, некоторым «компиляторам» не указ, ну да это другая история) всё выполнится, такой код сложнее читать другим. А если вы готовите задачу, довольно вероятно, что читать её исходники придётся ещё кому-то, когда что-то пойдёт не так.

    • »
      »
      »
      10 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      (1-2) А почему, собственно, влазить во имя кроссплатформенности в генерацию случайных чисел и в чтение файлов можно, а в min / max нельзя? В чём великая и принципиальная разница?

      Вот стОит ли добиваться, чтоб именно min / max стали кроссплатформенными — тут я возможно неправ. Как-то забыл во время написания стартового комментария о том, что вообще-то есть __testlib_min и __testlib_max, определённые гарантированно как функции, и можно пользоваться ими. Но почему факт наличия rnd.next versus rand() надо выпячивать аж до compile error-ов на rand(), а об этой проблеме с min / max вообще никак не сообщать — всё равно непонятно.

      (3) Если не годится ранее приведённый пример MS VC++ Express 2008 — ну, в Microsoft Visual Studio Express 2012 for Windows Desktop то же самое. Или опять недостаточно современно?

      (4) И при чём тут "больше одной функции с побочными эффектами "? Будет написано какое-нибудь

      M = max(M, inf.readInt())
      

      --- та же проблема будет ещё пожёстче. Или тут Вы тоже видите "больше одной"?

      • »
        »
        »
        »
        10 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        (1-2) А почему, собственно, влазить во имя кроссплатформенности в генерацию случайных чисел и в чтение файлов можно, а в min / max нельзя? В чём великая и принципиальная разница?

        Да можно. Вопрос в том, где остановиться. Наверняка же это не единственные «сломанные» функции. Из недостатков подхода «починим всё» могу отметить, что уже при нынешнем 90-килобайтном testlib.h чекеры и валидаторы компилируются ненулевое время.


        (3) Ну, печально, что в вижаке min и max не для людей сделаны. Не могу придумать современный use case для этого, кроме обратной совместимости.

        А какая-нибудь версия Visual Studio реализует какой-нибудь стандарт C++?


        (4) Это была подколка про ваш пример, в котором написано дословно следующее:

        return max(1, (int)(10*rnd.next(1.0)*rnd.next(1.0)));
        

        Какой порядок у двух вызовов rnd.next? Насколько я понимаю, он не специфицирован.

        Здесь-то неважно, потому что умножение коммутативно (кстати, правда? или для трёх и более умножений вещественных чисел — уже нет?), но вот какая-нибудь похожая строка типа

        return Point (rnd.next (1.0), rnd.next (1.0));
        

        имеет право вычислить координаты точки в произвольном порядке. Поэтому не надо так писать в генераторах.