пятница, 17 ноября 2017 г.

[prog.c++.thoughts] Какой вклад в сложность C++ вносит борьба с самой сложностью C++?

Давеча один из интересующихся SObjectizer-ом пользователей задал вопрос: а можно ли использовать в SO-5 в качестве обработчиков сообщений const-методы? Вопрос оказался, что называется, внезапным. Мы используем SO-5 в разработке софта уже лет семь (как раз где-то в конце 2010 первые стабильные версии SO-5 пошли в дело, если мне не изменяет склероз). Но ни разу нам не пришлось столкнуться с тем, чтобы обработчик сообщения следовало бы пометить модификатором const. Видимо, это потому, что обработчики сообщений в подавляющем большинстве случаев меняют состояние агента, поэтому не было смысла использовать const-методы в качестве обработчиков. А тут новый человек, другие задачи, другой взгляд на решение этих задач. И const-методы оказались нужны.

Ну, OK. Какие проблемы, будем делать.

Только вот как это сделать так, чтобы трудоемкость и сопровождаемость получившегося решения не зашкалила? Речь идет о том, чтобы преобразовать 6-7 методов вот такого формата:

templateclass RESULT, class ARG, class AGENT >
subscription_bind_t &
event(
   RESULT (AGENT::*pfn)( ARG ),
   thread_safety_t thread_safety = not_thread_safe );

которые живут в SO-5 давно, и используются повсеместно.

Нужно было получить методы, которые бы могли принять как RESULT(AGENT::*pfn)(ARG), так и RESULT(AGENT::*pfn)(ARG) const.

Простейший вариант, который приходит в голову -- это тупое дублирование:

четверг, 16 ноября 2017 г.

[prog.c++] Наткнулся на странное в VC++12

Разбавляя код SObjectizer-а шаблонной магией наткнулся на странную штуку в VC++12 (который из VS2013). Такое ощущение, что когда вложенность лямбд оказывается достаточно большой и очередная лямбда не захватывает контекст (т.е. может быть конвертирована в обычную функцию), то VC++ в качестве типа лямбды начинает использовать не анонимный класс с operator(), а указатель на функцию с соответствующей сигнатурой. Т.е. тупо конвертирует лямбду в функцию и дальше использует тип этой функции в качестве типа лямбды. Проявилось это вот на такой строчке. Там реально лямбда на лямбде и лямбдой погоняет :)

К счастью, в более новых версиях VC++ уже такого нет, там лямбда остается лямбдой. Надо бы закругляться с поддержкой VC++12.0. Видимо, в 2018 мы перестанем поддерживать этот компилятор в SO-5.

Еще захотелось, чтобы в C++ таки концепты завезли. Ибо набирать такое, наверное, прикольно, но уж больно муторно:

templatetypename Method_Pointer >
typename std::enable_if<
      details::is_agent_method_pointer<Method_Pointer>::value,
      subscription_bind_t & >::type
event(
   Method_Pointer pfn,
   thread_safety_t thread_safety = not_thread_safe );

Хотелось бы один раз определить концепт MethodAsHandler, а потом указывать этот концепт в декларации метода:

subscription_bind_t &
event(
   MethodAsPointer pfn,
   thread_safety_t thread_safety = not_thread_safe );

Осталось каких-то три годика подождать... :)

понедельник, 13 ноября 2017 г.

[prog.c++] Полку парсеров аргументов командной строки для современного C++ прибыло

Давеча на глаза попалась информация о двух новых библиотеках для работы с аргументами командной строки в современном C++. Поскольку в G+ ленте нет возможности поиска по своим же собственным постам (что печально), то зафиксирую информацию об этих библиотеках в блоге, дабы было проще найти при случае. Сразу скажу, я эти библиотеки пока не использовал, даже в код не заглядывал. Но судя по тому, что видно на поверхности, выглядит очень вкусно.

Итак, первая библиотека -- Clara от автора отличного инструмента для TDD и BDD в C++, Catch/Catch2 (с недавних пор библиотека называется именно Catch2). Clara изначально развивалась внутри Catch, но при подготовке Catch2 ее выделили в отдельный проект. Получается, что Clara является более-менее проверенной библиотекой, все-таки Catch2 используется весьма активно. Плюс работает на разных платформах и с разными компиляторами. Но, с другой стороны, документация пока оставляет желать много лучшего.

Пример того, как может выглядеть работа с Clara я выдрал из unit-тестов для нее:

using namespace clara;
struct TestOpt {
    std::string processName;
    std::string fileName;
    int number = 0;
    int index = 0;
    bool flag = false;
    std::string firstPos;
    std::string secondPos;
    std::vector<std::string> unpositional;

    auto makeCli() -> Parser {
        return ExeName( processName )
          | Opt( fileName, "filename" )
              ["-o"]["--output"]
              ( "specifies output file" )
          | Opt( number, "an integral value" )
              ["-n"]
          | Opt( [&]( int i ) {
                    if (i < 0 || i > 10)
                        return ParserResult::runtimeError("index must be between 0 and 10");
                    else {
                        index = i;
                        return ParserResult::ok( ParseResultType::Matched );
                    }
                }, "index" )
              ["-i"]
              ( "An index, which is an integer between 0 and 10, inclusive" )
          | Opt( flag )
              ["-f"]
              ( "A flag" )
          | Arg( firstPos, "first arg" )
              ( "First position" )
          | Arg( secondPos, "second arg" )
              ( "Second position" );
    }
};

Здесь метод TestOpt::makeCli как раз и создает парсер аргументов командной строки, который сохраняет разобранные значения внутри экземпляра структуры TestOpt.

Как и положено такого рода инструментам Clara автоматически генерирует usage-string, который можно отобразить пользователю.


Второй инструмент -- это clipp. Так же header-only, так же ему достаточно только стандартной библиотеки современного C++. Средства описания аргументов командной строки похожи на таковые в Clara, но, как мне показалось, здесь все более продвинуто:

#include <iostream>

#include "clipp.h"

using namespace clipp;
using std::cout;
using std::string;

int main(int argc, char* argv[]) { 
    bool rec = false, utf16 = false;
    string infile = "", fmt = "csv";

    auto cli = (
        value("input file", infile),
        option("-r""--recursive").set(rec).doc("convert files recursively"),
        option("-o") & value("output format", fmt),
        option("-utf16").set(utf16).doc("use UTF-16 encoding")
    );

    if(!parse(argc, argv, cli)) cout << make_man_page(cli, argv[0]);
    // ...
}

Если с аргументами что-то не так, то можно автоматически сгенерировать вот такой текст:

SYNOPSIS
    convert <input file> [-r] [-o <output format>] [-utf16]

OPTIONS
    -r, --recursive  convert files recursively
    -utf16           use UTF-16 encoding

Библиотека использует перегрузку операторов, что позволяет записывать вот такие-вот DSL-и:

auto cli = ( 
    option("-b")          % "activates b"   >> b,             
    option("-c""--noc") % "deactivates c" >> set(c,false),
    option("--hi")        % "says hi"       >> []{cout << "hi!\n";} );

Вообще, документация оставляет самые приятные впечатления. А вот по поводу кода на Reddit-е высказали замечания. С которыми автор библиотеки согласился и пояснил, что разработка еще не завершена и, например, под MSVC он пока особо ничего не проверял. Так что дока хорошая, а вот либа еще сырая. Хотя желание попробовать вызывает.