boost::program_options: перебор и печать всех опций

Я недавно начал использовать boost::program_options и нашел это очень удобным. Тем не менее, мне не хватает одной вещи, которую я не смог правильно написать:

Я хотел бы перебрать все варианты, которые были собраны в boost::program_options::variables_map вывести их на экран. Это должно стать удобной функцией, которую я могу просто вызвать, чтобы вывести список всех опций, которые были установлены, без необходимости обновлять функцию, когда я добавляю новые опции или для каждой программы.

Я знаю, что могу проверять и выводить отдельные параметры, но, как сказано выше, это должно стать общим решением, которое не учитывает фактические параметры. Я также знаю, что могу перебирать содержимое variables_map так как это просто расширенный std::map, Я мог бы тогда проверить тип контакта в хранимых boost::any переменная и использовать .as<> преобразовать его обратно в соответствующий тип. Но это будет означать кодирование длинного блока переключения с одним регистром для каждого типа. И это не похоже на хороший стиль кодирования для меня.

Итак, вопрос в том, есть ли лучший способ перебрать эти параметры и вывести их?

4 ответа

Как упоминалось ранее @Rost, шаблон Visitor является хорошим выбором здесь. Чтобы использовать его с ПО, вам нужно использовать уведомители для ваших опций таким образом, чтобы, если опция была пропущена, уведомитель заполнял запись в вашем наборе boost::variant ценности. Набор должен храниться отдельно. После этого вы можете перебирать свой набор и автоматически обрабатывать действия (например, печатать) над ними, используя boost::apply_visitor,

Для посетителей наследовать от boost::static_visitor<>

На самом деле, я сделал использование Visitor и универсального подхода более широким.

Я создал class MyOption который содержит описание, boost::variant для значения и другие параметры, такие как неявный, по умолчанию и так далее. Я заполняю вектор объектов типа MyOption так же, как PO делают для своих вариантов (см. boost::po::options_add()через шаблоны. В момент прохождения std::string() или же double() за boosts::varianПри инициализации вы указываете тип значения и другие вещи, такие как default, неявные.

После этого я использовал шаблон посетителя, чтобы заполнить boost::po::options_description контейнер с boost::po нужны собственные структуры для анализа входной командной строки. Во время заполнения я установил уведомитель для каждого варианта - если он будет передан boost::po автоматически заполнит мой оригинальный объект MyOption,

Далее вам нужно выполнить po::parse а также po::notify, После этого вы сможете использовать уже заполненный std::vector<MyOption*> через шаблон посетителя, так как он содержит Boost:: вариант внутри.

Что хорошего во всем этом - вы должны написать свой тип параметра только один раз в коде - при заполнении вашего std::vector<MyOption*>,

PS. если при использовании этого подхода вы столкнетесь с проблемой настройки уведомителя для параметра без значения, обратитесь к этой теме, чтобы получить решение: boost-program-options: уведомитель для параметров без значения

PS2. Пример кода:

std::vector<MyOptionDef> options;
OptionsEasyAdd(options)
  ("opt1", double(), "description1")
  ("opt2", std::string(), "description2")
...
;
po::options_descripton boost_descriptions;
AddDescriptionAndNotifyerForBoostVisitor add_decr_visitor(boost_descriptions);
// here all notifiers will be set automatically for correct work with each options' value type
for_each(options.begin(), options.end(), boost::apply_visitor(add_descr_visitor));  

Это хороший случай, чтобы использовать шаблон посетителя. к несчастью boost::any не поддерживает шаблон посетителя, как boost::variant делает. Тем не менее, есть некоторые сторонние подходы.

Другая возможная идея - использовать RTTI: создать карту type_info известных типов, сопоставленных с функтором обработчика типов.

Так как вы собираетесь просто распечатать их в любом случае, вы можете получить исходное строковое представление при разборе. (вероятно, в коде есть ошибки компилятора, я вырвал его из своей кодовой базы и не определил кучу вещей)

std::vector<std::string> GetArgumentList(const std::vector<boost::program_options::option>& raw)
{
    std::vector<std::string> args;

    BOOST_FOREACH(const boost::program_options::option& option, raw)
    {
        if(option.unregistered) continue; // Skipping unknown options

        if(option.value.empty())
            args.push_back("--" + option.string_key));
        else
        {
            // this loses order of positional options
            BOOST_FOREACH(const std::string& value, option.value)
            {
                args.push_back("--" + option.string_key));
                args.push_back(value);
            }
        }
    }

    return args;
}

Использование:

boost::program_options::parsed_options parsed = boost::program_options::command_line_parser( ...

std::vector<std::string> arguments = GetArgumentList(parsed.options);
// print

Я имел дело именно с этим типом проблемы сегодня. Это старый вопрос, но, возможно, это поможет людям, которые ищут ответ.

Метод, который я придумал, состоит в том, чтобы попробовать кучу as<...>(), а затем игнорировать исключение. Это не очень красиво, но я заставил его работать.

В приведенном ниже блоке кода vm - это переменная_карта из boost program_options. vit является итератором над vm, что делает его парой std:: string и boost::program_options::variable_value, причем последний является boost:: any. Я могу напечатать имя переменной с помощью vit->first, но vit->second не так просто вывести, потому что это boost:: any, т. Е. Исходный тип потерян. Некоторые должны быть преобразованы как std:: string, некоторые как двойные и так далее.

Итак, чтобы рассчитать значение переменной, я могу использовать это:

std::cout << vit->first << "=";
try { std::cout << vit->second.as<double>() << std::endl;
} catch(...) {/* do nothing */ }
try { std::cout << vit->second.as<int>() << std::endl;
} catch(...) {/* do nothing */ }
try { std::cout << vit->second.as<std::string>() << std::endl;
} catch(...) {/* do nothing */ }
try { std::cout << vit->second.as<bool>() << std::endl;
} catch(...) {/* do nothing */ }

У меня есть только 4 типа, которые я использую для получения информации из файла командной строки / конфигурации, если бы я добавил больше типов, мне пришлось бы добавить больше строк. Я признаю, что это немного некрасиво.

Другие вопросы по тегам