How to store data in boost::program_options::variable_map? - c++

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!

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

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)

Related

boost::program_options generating MLK.MUST in Klocwork

I wrote the following C++ code based on the Boost library to take inputs from command line.
#include <iostream>
#include <boost/program_options.hpp>
#include <boost/format.hpp>
using namespace std;
namespace po = boost::program_options;
int main(int argc, char** argv) {
int party = 2, port = 9999;
string server_ip;
po::options_description desc{"Allowed options"};
desc.add_options() //
("help,h", "produce help message") //
("party,k", po::value<int>(&party)->default_value(1), "party id: 1 for server, 2 for client") //
("port,p", po::value<int>(&port)->default_value(1234), "socket port") //
("server_ip,s", po::value<string>(&server_ip)->default_value("localhost"), "server's IP.");
po::variables_map vm;
try {
po::parsed_options parsed = po::command_line_parser(argc, argv).options(desc).allow_unregistered().run();
po::store(parsed, vm);
if (vm.count("help")) {
cout << desc << endl;
return 0;
}
po::notify(vm);
}catch (po::error& e) {
cout << "ERROR: " << e.what() << endl << endl;
cout << desc << endl;
return -1;
}
cout << party << endl;
cout << port << endl;
cout << server_ip << endl;
}
It works as intended. However, Klocwork reported the following error (I have adjusted the line numbers for this code snippet):
main.cpp:16 MLK.MUST (2:Error) Analyze
Memory leak. Dynamic memory stored in 'po::value<int> ( &party)' allocated through function 'value<int>' at line 14 is lost at line 16
* main.cpp:14: Dynamic memory stored in 'po::value<int> ( &party)' is allocated by calling function 'value<int>'.
* value_semantic.hpp:198: 'r' is allocated by function 'new'.
* main.cpp:16: Dynamic memory stored in 'po::value<int> ( &party)' is lost.
Current status 'Analyze'
I found this old post boost program_options generating a Klocwork MLK.MUST. However, after reading the answer, I still do not know how I can solve this issue.
Another issue reported by Klocwork is 'port' is used uninitialized in this function. It specifically mentions that passing '&port' to 'po::value<int>' does not initialize 'port'. However, after running the code, I see that it does initialize port since the value of port is printed as 1234 and not 9999.
Is there a way to write this code that will solve the above issues?
Another issue reported by Klocwork is 'port' is used uninitialized in this function. It specifically mentions that passing '&port' to 'po::value'
This is a false positive: nothing uses the value of port before it's initialized (I checked). However, it should be enough to actually initializing port to silence the message, by. It's weird that it still triggers, since you already had that.
Neither vaglrind nor ASAN+UBSAN find anything wrong with the code for me. Here's a brute force test that tries all kinds of option combinations (including unregistered and erroneous):
#!/bin/bash
set -e -u
opts=( '' '-k two' '-k 2' '-p 2345' '-s 127.0.0.88' 'bogus' '--more-bogus');
for a in "${opts[#]}"
do
for b in "${opts[#]}"
do
for c in "${opts[#]}"
do
valgrind ./sotest "$a" "$b" "$c"
done
done
done
That ends up running 343 different invocations of the program and ends up printing the expected outputs:
69x 2
69x 2345
69x 127.0.0.88
99x 1
99x 1234
99x localhost
The expected diagnostics:
17x option '--port' cannot be specified more than once
17x option '--server_ip' cannot be specified more than once
34x option '--party' cannot be specified more than once
107x the argument ('two') for option '--party' is invalid
And most importantly, consistent leak-free report:
343 All heap blocks were freed -- no leaks are possible
TL;DR
I don't know why your tooling reports leaks. At the very least, the "'port' is used uninitialized" issue seems wrong on close inspection.
I tested on GCC 10 with Boost 1.73.0, -std=c++17 -O3, your source 1--% unaltered.
I hope this gives you more ideas and perhaps some reassurance.

boost program options count number of occurrences of a flag

I am trying to program in a way for the user of my program to specify the level of verbosity of my program from 0 to 3. I was told by someone that there might be a way to set up the program options so that I am able to detect the number of occurrences of a flag, and then run my program accordingly.
Example:
[none] -> level 0
-v -> level 1
-vv -> level 2
-vvv -> level 3
Does anyone know if this is possible? Do I just need to set up three different options, one for each possibility? I have tried to search around for a similar example however I fear I may be searching the wrong things.
I can't think of a nice way. boost::program_options option syntax is a little more structured (and arguably more simplistic) than getopt.
Here's one way:
#include <boost/program_options.hpp>
#include <iostream>
#include <algorithm>
#include <cstdlib>
int main(int argc, char**argv)
{
namespace po = boost::program_options;
std::string verbosity_values;
po::options_description desc("Command Line Options");
desc.add_options()("verbosity,v",
po::value(&verbosity_values)->implicit_value(""),
"verbose");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("verbosity")) {
verbosity_values += "v";
}
if (std::any_of(begin(verbosity_values), end(verbosity_values), [](auto&c) { return c != 'v'; })) {
std::cerr << "invalid verbosity" << std::endl;
std::exit(100);
}
std::cout << "verbosity: " << verbosity_values.size() << std::endl;
}
How it works:
define an option called "--verbosity" with a synonym "-v".
'verbosity' takes a string argument, which we default to ""
we check that the string contains only 'v's
if the variables_map contains a 'verbosity' argument, then -v or --verbosity must have been mentioned on the command line. Therefore, add a 'v' to the string.
verbosity is the length of the string.
example:
$ ./a.out -vvvv
verbosity: 4
$

How do I get default argument values with boost program options?

I want to use default values for some of my command line arguments. How do I tell program_options what the default option is, and, if the user doesn't supply the argument, how do I tell my program to use the default value?
Say I want to have an argument specifying the number of robots to send on a murderous rampage with a default value of 3.
robotkill --robots 5 would produce 5 robots have begun the silicon revolution, whereas
robotkill (no arguments supplied) would produce 3 robots have begun the silicon revolution.
program_options automatically assigns default values to options when the user doesn't supply those options. You don't even need to check whether the user supplied a given option, just use the same assignment in either case.
#include <iostream>
#include <boost/program_options.hpp>
namespace po = boost::program_options;
int main (int argc, char* argv[]) {
po::options_description desc("Usage");
desc.add_options()
("robots", po::value<int>()->default_value(3),
"How many robots do you want to send on a murderous rampage?");
po::variables_map opts;
po::store(po::parse_command_line(argc, argv, desc), opts);
try {
po::notify(opts);
} catch (std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
return 1;
}
int nRobots = opts["robots"].as<int>();
// automatically assigns default when option not supplied by user!!
std::cout << nRobots << " robots have begun the silicon revolution"
<< std::endl;
return 0;
}

boost::program_options - does it do an exact string matching for command line options?

There seems to be a problem the way boost::program_options's options_description matching is done.
int main(int argc, char* argv[])
{
boost::program_options::options_description desc("CmdLine utility");
desc.add_options()
("hel", "hel message")
("help", "produce help message")
("helps","helps message")
;
boost::program_options::variables_map vm;
boost::program_options::store(boost::program_options::parse_command_line(argc, argv,desc), vm);
boost::program_options::notify(vm);
if(vm.count("help")) {
std::cout << desc << std::endl;
}
if(vm.count("helps")) {
std::cout << "helps..." << std::endl;
}
if(vm.count("hel")) {
std::cout << "hel..." << std::endl;
}
return 0;
}
Output -
C:\code>cmd.exe --helps
helps...
C:\code>cmd.exe --help
helps...
C:\code>cmd.exe --hel
helps...
The output changes if I change the order in which options are added using add_options() call. Also it looks like program_options does not do a complete command string matching, so even if you enter a substring of the option, it will consider it as a valid option without doing a complete string comparison. If this is a boost::program_options feature, is there any way to force it to do exact string matching rather than doing it with substring matching ? (I am using Boost version 1.42)
By default, program_option has allow_guessing style bit on, so a substring match is sufficient. The behaviour you observe, where an option is matching a prefix of the command line, even when there's a different option that matches fully, is a bug. It's fixed in 1.45.
Maybe you called wrongly. You example is fine. Look at the output I got :
[vladimir#asa example]$ ./a.out --help
CmdLine utility:
--hel hel message
--help produce help message
--helps helps message
[vladimir#asa example]$ ./a.out --hel
hel...
[vladimir#asa example]$ ./a.out --helps
helps...

How to accept empty value in boost::program_options

I'm using boost::program_options library to process command line params.
I need to accept a file name via -r option, in case if it is empty (-r given without params) I need to use stdin.
desc.add_options()
("replay,r", boost::program_options::value<std::string>(), "bla bla bla")
In this case boost wouldn't accept -r without params and throw an exception.
default_value () option does not work as well as it would make library return value even if user didn't give -r option.
Any ideas how to work around?
Please use the implicit_value method, e.g
desc.add_options()
("replay,r", po::value<std::string>()->implicit_value("stdin"), "bla bla bla")
This makes the option accept either 0 or 1 token, and if no tokens are provided, it will act as if 'stdin' was provided. Of course, you can pick any other implicit value -- including empty string and '-' as suggested by mch.
You could try a trick with the multitoken and zero_tokens options:
using namespace std;
namespace po = boost::program_options;
vector<string> replay;
po::options_description desc("Allowed options");
desc.add_options()
("replay,r", po::value< vector<string> >(&replay)->multitoken()->zero_tokens(), "bla bla bla");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("replay"))
{
size_t s = vm["replay"].as< vector<string> >().size();
if (s == 0)
cout << "replay without args" << endl;
else if (s == 1)
cout << "replay with one arg" << endl;
else
cout << "replay with multiple args" << endl;
}
else
cout << "replay not specified" << endl;
Then just count the number of elements in the replay vector. You'll want to throw an error if multiple arguments are passed to the replay option.
I don't think any command line parsing libraries allow you to have options that can either take an argument or not. If an option requires an argument, you must give one. In this case, the standard practice (in *NIX anyway) is to use '-' as a filename to denote that you want to read from standard input.