boost::program_options : iterating over and printing all options
Asked Answered
H

4

14

I have recently started to use boost::program_options and found it to be highly convenient. That said, there is one thing missing that I was unable to code myself in a good way:

I would like to iterate over all options that have been collected in a boost::program_options::variables_map to output them on the screen. This should become a convenience function, that I can simply call to list all options that were set without the need to update the function when I add new options or for each program.

I know that I can check and output individual options, but as said above, this should become a general solution that is oblivious to the actual options. I further know that I can iterate over the contents of variables_map since it is simply an extended std::map. I could then check for the type containd in the stored boost::any variable and use .as<> to convert it back to the appropriate type. But this would mean coding a long switch block with one case for each type. And this doesn't look like good coding style to me.

So the question is, is there a better way to iterate over these options and output them?

Harijan answered 3/9, 2012 at 8:22 Comment(0)
N
7

As @Rost previously mentioned, Visitor pattern is a good choice here. To use it with PO you need to use notifiers for your options in such a way that if option is passed notifier will fill an entry in your set of boost::variant values. The set should be stored separately. After that you could iterate over your set and automatically process actions (i.e. print) on them using boost::apply_visitor.

For visitors, inherit from boost::static_visitor<>

Actually, I made Visitor and generic approach use more broad.

I created a class MyOption that holds description, boost::variant for value and other options like implicit, default and so on. I fill a vector of objects of the type MyOption in the same way like PO do for their options (see boost::po::options_add()) via templates. In the moment of passing std::string() or double() for boosts::variant initialization you fill type of the value and other things like default, implicit.

After that I used Visitor pattern to fill boost::po::options_description container since boost::po needs its own structures to parse input command line. During the filling I set notifyer for each option - if it will be passed boost::po will automatically fill my original object of MyOption.

Next you need to execute po::parse and po::notify. After that you will be able to use already filled std::vector<MyOption*> via Visitor pattern since it holds boost::variant inside.

What is good about all of this - you have to write your option type only once in the code - when filling your std::vector<MyOption*>.

PS. if using this approach you will face a problem of setting notifyer for an option with no value, refer to this topic to get a solution: boost-program-options: notifier for options with no value

PS2. Example of code:

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));  
Nim answered 4/9, 2012 at 17:15 Comment(1)
Thank you for the thorough explanation. This is also an interesting solution. In this way I could also easily add different descriptions for the help output and when simply listing the option values.Harijan
M
5

It's a good case to use Visitor pattern. Unfortunately boost::any doesn't support Visitor pattern like boost::variant does. Nevertheless there are some 3rd party approaches.

Another possible idea is to use RTTI: create map of type_info of known types mapped to type handler functor.

Mayhap answered 3/9, 2012 at 8:58 Comment(1)
Thank you for the link and the idea about RTTI. I was hoping that I could prevent building a structure for all supported types that I would have to manage if the types increase, but it seems this will not be possible. Basically, I wanted to pass the buck to the types - like if they support 'operator<<' everything works okay, otherwise compilation should fail.Harijan
W
2

Since you are going to just print them out anyway you can grab original string representation when you parse. (likely there are compiler errors in the code, I ripped it out of my codebase and un-typedefed bunch of things)

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;
}

Usage:

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

std::vector<std::string> arguments = GetArgumentList(parsed.options);
// print
Widely answered 4/9, 2014 at 16:14 Comment(0)
S
1

I was dealing with just this type of problem today. This is an old question, but perhaps this will help people who are looking for an answer.

The method I came up with is to try a bunch of as<...>() and then ignore the exception. It's not terribly pretty, but I got it to work.

In the below code block, vm is a variables_map from boost program_options. vit is an iterator over vm, making it a pair of std::string and boost::program_options::variable_value, the latter being a boost::any. I can print the name of the variable with vit->first, but vit->second isn't so easy to output because it is a boost::any, ie the original type has been lost. Some should be cast as a std::string, some as a double, and so on.

So, to cout the value of the variable, I can use this:

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 */ }

I only have 4 types that I use to get information from the command-line/config file, if I added more types, I would have to add more lines. I'll admit that this is a bit ugly.

Shahaptian answered 4/9, 2014 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.