boost::program_options "polymorphic" argument
Asked Answered
S

4

7

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?

Struve answered 16/4, 2012 at 14:28 Comment(3)
Make the argument a string and add a little parsing function.Therapsid
@KerrekSB: That would have been my initial approach too, but I don't find it very type-safe and it requires me to write quite some code.Struve
+1 since this is a common question when using the program_options library that I don't think is very well explained in the documentationMedardas
S
1

I am posting this code here, hoping it will prove useful to somebody. It is the "templatized" version of Sam Miller's answer.

#ifndef RANDOMCONSTANT_HH
#define RANDOMCONSTANT_HH

#include <boost/random.hpp>

boost::random::mt19937 g_randomConstantPrng(static_cast<unsigned int>(std::time(NULL) + getpid()));

template<typename T>
class RandomConstant
{
public:
    RandomConstant() { /* nothing */ }
    RandomConstant(T value) : _value(value) { /* nothing */ }
    RandomConstant(int low, int high)
    {
        boost::random::uniform_int_distribution<> dist(low, high);
        _value = dist(g_randomConstantPrng);
    }
    RandomConstant(double low, double high)
    {
        boost::random::uniform_real_distribution<> dist(low, high);
        _value = dist(g_randomConstantPrng);
    }
    T value() const { return _value; }

private:
    T _value;
};


template<typename T>
std::ostream&
operator<<(std::ostream& os, const RandomConstant<T>& foo)
{
    os << foo.value();
    return os;
}

template<typename T>
std::istream&
operator>>(std::istream &is, RandomConstant<T> &foo)
{
    std::string line;
    std::getline(is, line);
    if (!is) return is;

    const std::string::size_type comma = line.find_first_of( ',' );
    if (comma != std::string::npos)
    {
        const T low = boost::lexical_cast<T>( line.substr(0, comma) );
        const T high = boost::lexical_cast<T>( line.substr(comma + 1) );
        foo = RandomConstant<T>(low, high);
    }
    else
    {
        foo = RandomConstant<T>(boost::lexical_cast<T>(line));
    }

    return is;
}

#endif /* RANDOMCONSTANT_HH */

Used as follows:

namespace po = boost::program_options;
po::options_description desc;
desc.add_options()
    ("help", "show help")
    ("intValue", po::value<RandomConstant<int>>()->default_value(3), "description 1")
    ("doubleValue", po::value<RandomConstant<double>>()->default_value(1.5), "description 2")
;

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

if (vm.count("help")) {
    std::cerr << desc << std::endl;
    return EXIT_FAILURE;
}

int intValue = vm["intValue"].as<RandomConstant<int>>().value();
double doubleValue = vm["doubleValue"].as<RandomConstant<double>>().value();
Struve answered 17/4, 2012 at 8:14 Comment(0)
M
4

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$ 
Medardas answered 16/4, 2012 at 15:42 Comment(3)
I'm getting the following error: /usr/include/boost/lexical_cast.hpp:1147:61: error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’Struve
@user what platform? My example compiles cleanly on Linux and Mac OS X using boost 1.49Medardas
Sorry about the previous comment, I tried to copy-templetize-paste and it didn't work very smoothly.Struve
P
1

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.

Patton answered 16/4, 2012 at 15:4 Comment(0)
S
1

I am posting this code here, hoping it will prove useful to somebody. It is the "templatized" version of Sam Miller's answer.

#ifndef RANDOMCONSTANT_HH
#define RANDOMCONSTANT_HH

#include <boost/random.hpp>

boost::random::mt19937 g_randomConstantPrng(static_cast<unsigned int>(std::time(NULL) + getpid()));

template<typename T>
class RandomConstant
{
public:
    RandomConstant() { /* nothing */ }
    RandomConstant(T value) : _value(value) { /* nothing */ }
    RandomConstant(int low, int high)
    {
        boost::random::uniform_int_distribution<> dist(low, high);
        _value = dist(g_randomConstantPrng);
    }
    RandomConstant(double low, double high)
    {
        boost::random::uniform_real_distribution<> dist(low, high);
        _value = dist(g_randomConstantPrng);
    }
    T value() const { return _value; }

private:
    T _value;
};


template<typename T>
std::ostream&
operator<<(std::ostream& os, const RandomConstant<T>& foo)
{
    os << foo.value();
    return os;
}

template<typename T>
std::istream&
operator>>(std::istream &is, RandomConstant<T> &foo)
{
    std::string line;
    std::getline(is, line);
    if (!is) return is;

    const std::string::size_type comma = line.find_first_of( ',' );
    if (comma != std::string::npos)
    {
        const T low = boost::lexical_cast<T>( line.substr(0, comma) );
        const T high = boost::lexical_cast<T>( line.substr(comma + 1) );
        foo = RandomConstant<T>(low, high);
    }
    else
    {
        foo = RandomConstant<T>(boost::lexical_cast<T>(line));
    }

    return is;
}

#endif /* RANDOMCONSTANT_HH */

Used as follows:

namespace po = boost::program_options;
po::options_description desc;
desc.add_options()
    ("help", "show help")
    ("intValue", po::value<RandomConstant<int>>()->default_value(3), "description 1")
    ("doubleValue", po::value<RandomConstant<double>>()->default_value(1.5), "description 2")
;

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

if (vm.count("help")) {
    std::cerr << desc << std::endl;
    return EXIT_FAILURE;
}

int intValue = vm["intValue"].as<RandomConstant<int>>().value();
double doubleValue = vm["doubleValue"].as<RandomConstant<double>>().value();
Struve answered 17/4, 2012 at 8:14 Comment(0)
H
0

You can probably use multitoken

po::options_description desc("Allowed options");
desc.add_options()
    ("nmax", po::value< std::vector< float > >()->multitoken()->default_value(10), "description")
;

...

float value;
if (vm.count["nmax"] == 2)
    value = random value ...
else
    value = vm["nmax"].as< std::vector< float > >()[0];
Hanging answered 18/4, 2013 at 5:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.