I would like to use boost::program_options to create an executable which can be called as follows:
./example --nmax=0,10 # nmax is chosen randomly between 0 and 10
./example --nmax=9 # nmax is set to 9
./example # nmax is set to the default value of 10
What is the best way to achieve this, in a type-safe way, with minimum code?
I would like to use boost::program_options to create an executable which can be called as follows:
the program_options
library is very flexible, this can easily be supported by writing your own class with stream insertion and extraction operators.
#include <iostream>
#include <limits>
#include <stdlib.h>
#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>
class Max
{
public:
Max() :
_max( std::numeric_limits<int>::max() )
{
}
Max(
int max
) :
_max( max )
{
}
Max(
int low,
int high
)
{
int value = rand();
value %= (high - low);
value += low;
_max = value;
}
int value() const { return _max; }
private:
int _max;
};
std::ostream&
operator<<(
std::ostream& os,
const Max& foo
)
{
os << foo.value();
return os;
}
std::istream&
operator>>(
std::istream& is,
Max& foo
)
{
std::string line;
std::getline( is, line );
if ( !is ) return is;
const std::string::size_type comma = line.find_first_of( ',' );
try {
if ( comma != std::string::npos ) {
const int low = boost::lexical_cast<int>( line.substr(0, comma) );
const int high = boost::lexical_cast<int>( line.substr(comma + 1) );
foo = Max( low, high );
} else {
foo = Max( boost::lexical_cast<int>(line) );
}
} catch ( const boost::bad_lexical_cast& e ) {
std::cerr << "garbage when convering Max value '" << line << "'" << std::endl;
is.setstate( std::ios::failbit );
}
return is;
}
int
main( int argc, char** argv )
{
namespace po = boost::program_options;
Max nmax;
po::options_description options;
options.add_options()
("nmax", po::value(&nmax)->default_value(10), "random number range, or value" )
("help,h", po::bool_switch(), "help text")
;
po::variables_map vm;
try {
po::command_line_parser cmd_line( argc, argv );
cmd_line.options( options );
po::store( cmd_line.run(), vm );
po::notify( vm );
} catch ( const boost::program_options::error& e ) {
std::cerr << e.what() << std::endl;
exit( EXIT_FAILURE );
}
if ( vm["help"].as<bool>() ) {
std::cout << argv[0] << " [OPTIONS]" << std::endl;
std::cout << std::endl;
std::cout << "OPTIONS:" << std::endl;
std::cout << options << std::endl;
exit(EXIT_SUCCESS);
}
std::cout << "random value: " << nmax.value() << std::endl;
}
sample session
samm:stackoverflow samm$ ./a.out
random value: 10
samm:stackoverflow samm$ ./a.out --nmax 55
random value: 55
samm:stackoverflow samm$ ./a.out --nmax 10,25
random value: 17
samm:stackoverflow samm$
The library doesn't offer "polymorphic" argument types like you suggest. Each argument has exactly one type. If you want to make it have different values based on the syntax of the argument, you need to add that functionality yourself.
The easy way is to do as Kerrek's comment suggests and use a string, and then parse it afterward. It doesn't really take much code.
Another way is to use a custom validator. Make up a special type dedicated to this format of argument, and then write a validate
function that converts string values into values of your custom type. Throw an exception if validation fails; the Program_Options library will treat it just like a validation failure of any of the built-in types. I wrote an example validator in response to another question.
The code you'll write for this is pretty much the same code you'd write to parse the string after parsing the command line; it's just a matter of whether you build it into the argument type, or just process it afterward.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With