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 типа, которые я использую для получения информации из файла командной строки / конфигурации, если бы я добавил больше типов, мне пришлось бы добавить больше строк. Я признаю, что это немного некрасиво.