Boost program options iterate over variables_map
Asked Answered
S

4

8
po::options_description desc("This are the options that are available");
    desc.add_options()("help", "print help")(
        "deer", po::value<uint32_t>(), "set how many deer you want")(
        "rating", po::value<uint32_t>(), "how good ?")(
        "name", po::value<std::string>(), "and your name is ... ?");

po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);

in a following section of the code I tried to iterate over vm

for (const auto& it : vm) {
      std::cout << it.first.c_str() << " "
                << it.second.as<it.pair::second_type>() << "\n";
    }

The main point here is that vm contains keys of the same type, but values with different types, in this example I have uint32_t mixed with a std::string.

How I can iterate over this kind of containers ? I would like to avoid a verbose approach so I'm trying to just iterate over this data structure.

EDIT:

I forgot to write this down, but obviously

namespace po = boost::program_options;
Supine answered 8/1, 2014 at 23:36 Comment(0)
H
15

boost variable_map use boost::any as the value so you can try to use boost::any_cast<T> to find out the type. perhaps something like this

for (const auto& it : vm) {
  std::cout << it.first.c_str() << " ";
  auto& value = it.second.value();
  if (auto v = boost::any_cast<uint32_t>(&value))
    std::cout << *v;
  else if (auto v = boost::any_cast<std::string>(&value))
    std::cout << *v;
  else
    std::cout << "error";
}
Heddy answered 9/1, 2014 at 0:15 Comment(0)
D
6

boost::program_options::variable_map is essentially an std::map<std::string, boost::any>, which means it uses type erasure to store the values. Because the original type is lost, there's no way to extract it without casting it to the correct type. You could implement a second map that contains the option name as key, and the extraction function as value, allowing you to dispatch the value to the appropriate extractor at runtime.

using extractor = std::map<std::string, void(*)(boost::variable_value const&)>;

or

using extractor = std::map<std::string, 
                           std::function<void(boost::variable_value const&)>;

if your extractors are more complicated and will not convert to a simple function pointer. An example of an extractor that'll print a uint32_t is

auto extract_uint32_t = [](boost::variable_value const& v) {
                             std::cout << v.as<std::uint32_t>();
                        };

Then your loop would look like this:

for (const auto& it : vm) {
  std::cout << it.first.c_str() << " "
  extractor_obj[it.first](it.second) 
  std::cout << "\n";
}

Here's a live demo with some made up types, but it's close enough to your use case that you should be able to apply something similar.

Dylan answered 9/1, 2014 at 0:12 Comment(0)
R
-1

I haven't looked into the boost::program_options template specifically but you could probably do something generic like use template functions that parse the command differently depending on the type:

template <typename T>
void HandleCommand( T command )
{
    // Generic solution
}

template <>
void HandleCommand( const po::value<std::string>& command )
{
    // Do something with the string
}

template <>
void HandleCommand( const po::value<uint32_t>& command )
{
    // Do something with the unsigned int
}
Recalcitrate answered 8/1, 2014 at 23:57 Comment(0)
B
-1

I think a better approach than iterate over the parameters is to use positional_options_description.

Example use:

po::positional_options_description p;
desc.add_options()
(...);
p.add("opt1", 1);
p.add("opt2", 1);
p.add("opt3", 1);

if (vm.size() != 3)
{
    std::cerr << "Command must be have 3 parameters.\n";
    return 1;
}
Boltrope answered 10/6, 2019 at 8:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.