How to store data in boost::program_options::variable_map?
Asked Answered
C

2

3

I am currently trying to rework some code that was handed down to me. The original point of the code is to read a configuration file, and to set up the different options in the file in a boost::program_options::variable_map, which is then read throughout other parts of the code which is already working fine.

Here is the code I am trying to replace:

// Some helpful definitions
boost::program_options::variables_map vm;
std::string filecfg = "File_name";
std::ifstream ifs(filecfg.c_str());

// Setting options (This is command line example, but config file is the same)
boost::program_options::options_description df("Command Line");
df.add_options()
    ("help", "display help message")
    ("file-cfg,f", boost::program_options::value<std::string>(), "config file")
    ("version", "display version and revision number");

boost::program_options::parsed_options parsedc = boost::program_options::parse_config_file(ifs, df, true);
boost::program_options::store(parsedc, vm);
boost::program_options::notify(vm);
std::vector <std::string> unrc = boost::program_options::collect_unrecognized(parsedc.options, boost::program_options::include_positional)

My thinking it to simply replace the boost::program_options::parsed_options parsedc and create this object by myself. The problem I run into is simply that there is no documentation on how to do this. I think it is mostly because it is not designed to be used this way.

In any case, I am just looking to fill up the vm object with the options described in dc, and with values that I can hold in a separate data structure (like a vector).

Is it possible to simply add values to vm? Or do I have to go through a function such as boost::program_options::store()?

Any help would be greatly appreciated! Let me know if something is unclear, or if there is something you'd like me to try!

Thanks!

Cataract answered 7/9, 2020 at 15:26 Comment(0)
R
2

Yeah you can.

Be aware that you will have to decide how to "mock"/"fake" the other semantics of it though. (E.g. you might want to masquerade the options as having been defaulted)

Conceptually, variable_map would be a map<string, variable_value>. variable_value:

Class holding value of option. Contains details about how the value is set and allows to conveniently obtain the value.

Note also that because variable_value uses boost::any for storage you will have to be exact about the types you will store. (So, don't store "oops" if you need a std::string("ah okay")).

Here's a simple demo:

Live On Coliru

#include <boost/program_options.hpp>
#include <iostream>
#include <iomanip>

namespace po = boost::program_options;
using namespace std::string_literals;

int main(/*int argc, char** argv*/) {
    // Some helpful definitions
    po::variables_map vm;

    vm.emplace("file-cfg", po::variable_value("string"s, true));
    vm.emplace("log-level", po::variable_value(3, false));
    vm.emplace("option3", po::variable_value{});
    notify(vm);

    std::vector unrc = { "unrecognized"s, "options"s };

    for (auto& [name, value] : vm) {
        std::cout
            << "Name: " << name
            << std::boolalpha
            << "\tdefaulted:" << value.defaulted()
            << "\tempty:" << value.empty();

        if (typeid(std::string) == value.value().type())
            std::cout << " - string " << std::quoted(value.as<std::string>()) << "\n";
        else if (typeid(int) == value.value().type())
            std::cout << " - int " << value.as<int>() << "\n";
        else if (!value.empty())
            std::cout << " - unknown type\n";
    }
}

Prints

Name: file-cfg  defaulted:true  empty:false - string "string"
Name: log-level defaulted:false empty:false - int 3
Name: option3   defaulted:false empty:true
Roccoroch answered 8/9, 2020 at 0:31 Comment(0)
L
1

I warn you not to use vm.emplace(…, po::variable_value(…,…)).
It's quite deceptive: it will work to some extent but fail spectacularly elsewhere.

When you use po::store, it internally also produces a po::variable_value and copies the semantic of your option to a private field of the po::variable_value. There's no way to set this semantic yourself.
(see: https://github.com/boostorg/program_options/blob/develop/src/variables_map.cpp#L83).

Without the semantic you can't at least:

  • read the option from multiple sources
  • vm.notify() will not write the value to variables associated with the option

Here's an (arguably ugly) way that should avoid these issues:

po::variables_map vm;
po::options_description opts;
opts.add_options()("optName", …);
…
po::parsed_options parsedOpts(&opts);
pOpts.options.push_back(po::option("optName", {"optValue"}));
po::store(parsedOpts, vm);

First, one has to create a parsedOpts object that stores the description of options. Then, add an option name and list of values as strings, regardless of the option type. Finally, within po::store, the name and value(s) are parsed and stored in vm.


A complete working example:

#include <boost/program_options.hpp>
#include <iostream>
namespace po = boost::program_options;

int main(int ac, char **av) {
    int i = 0;
    po::variables_map vm;
    po::options_description opts;
    opts.add_options()("id", po::value<int>(&i)->default_value(1));

    int userInput;
    std::cin >> userInput;

    if (userInput == 2) {
        vm.emplace("id", po::variable_value(userInput, false));
    }

    if (userInput == 3) {
        po::parsed_options pOpts(&opts);
        pOpts.options.push_back(po::option("id", {std::to_string(userInput)}));
        po::store(pOpts, vm);
    }

    po::store(po::parse_command_line(ac, av, opts), vm);

    //po::store(po::parse_config_file("options.ini", opts, true), vm);

    po::notify(vm);

    std::cout << "id (notified): " << i << std::endl;
    std::cout << "id (from vm):  " << vm["id"].as<int>() << std::endl;
    return 0;
}

Typing 2 runs vm.emplace and yields:

id (notified): 0
id (from vm):  2

which is bad, for po::value<int>(&i) was ignored

Typing 2 and adding --id=4 command line argument yields:

terminate called after throwing an instance of 'boost::wrapexcept<boost::program_options::multiple_occurrences>'

which is bad, for you cannot use multiple sources

Typing 3 runs the po::store on hand-crafted po::parsed_options and yields:

id (notified): 3
id (from vm):  3

Typing 3 and adding --id=4 command line argument yields:

id (notified): 3
id (from vm):  3

Which is correct & expected, for the subsequent stores shall not replace values that were stored earlier
(see https://www.boost.org/doc/libs/1_80_0/doc/html/program_options/tutorial.html#id-1.3.30.4.5)

Leyes answered 7/9, 2022 at 0:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.