Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::program_options "polymorphic" argument

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?

like image 691
user1202136 Avatar asked Apr 16 '12 14:04

user1202136


2 Answers

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$ 
like image 140
Sam Miller Avatar answered Oct 13 '22 00:10

Sam Miller


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.

like image 26
Rob Kennedy Avatar answered Oct 12 '22 23:10

Rob Kennedy