4

Suppose I have a program using boost::program_options to parse command line arguments, and one has an unsigned value:

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

namespace po = boost::program_options;

int main(int argc, char* argv[]) {
    unsigned num;
    po::options_description desc;
    desc.add_options()
        ("num,n", po::value<unsigned>(&num), "Non-negative number");
    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);
    std::cout << "Number: " << num << '\n';
    return 0;
}

Then if I pass a negative value on the command line, it accepts it and wraps it around:

$ ./bponeg -n -1
Number: 4294967295

I would rather that negative numbers trigger an error (i.e. throw invalid_option_value) the same as it would if I wrote ./bponeg -n abc. From my own code after the parsing, it seems impossible to distinguish between the case where the user wrote -1 or if they wrote 4294967295.

Can I instruct the program_options parser to reject negative inputs for unsigned values?

Nick Matteo
  • 4,453
  • 1
  • 24
  • 35
  • 4
    Just read a signed value and check if it's negative. I'm not a friend of using unsigned types for anything but bit-fiddling anyways. (Ib4 the usual hate: Several members of the standards committee agree with me, see e.g. [this panel](https://channel9.msdn.com/Events/GoingNative/2013/Interactive-Panel-Ask-Us-Anything) 9:50, 42:40, 1:02:50 ) – Baum mit Augen Apr 22 '16 at 18:07
  • 3
    @BaummitAugen what about situation where unsigned value migh be outside of range of signed? – Revolver_Ocelot Apr 22 '16 at 18:11
  • 2
    @Revolver_Ocelot If you need the extra bit, and using the next bigger signed integer is not an option, you gotta do what you gotta do. Never happened to me so far though. vOv – Baum mit Augen Apr 22 '16 at 18:13
  • @Revolver_Ocelot If it is a command line argument, I doubt the user will be inputting anything that cannot be parsed into a `long long`. If that is the case, then read a string in then do internal parsing. – Cyb3rFly3r Apr 22 '16 at 18:14
  • @BaummitAugen: That's a good idea. You might as well make it an answer. The only issue it creates for me is extra "comparison between signed and unsigned integer expressions" warnings which I'll have to work around. – Nick Matteo Apr 22 '16 at 18:38
  • @Kundor Barry's answer already mentions my solution, you may just as well accept it. As for the warnings: I would just avoid unsigned types wherever possible (this is not consensus though). If you cannot or don't want to do this, you can `Boost.numeric_cast` the value you read to the unsigned type and then proceed as usual. – Baum mit Augen Apr 22 '16 at 18:43
  • @BaummitAugen: Yeah, I started writing my comment before that answer appeared. I can't avoid unsigned types when comparing to a container's `size()`, and I'm operating on the principle that one should not let warnings persist. I'll use a cast when necessary. – Nick Matteo Apr 22 '16 at 18:55
  • @Kundor Yeah, I'm afraid the use of unsigned types in the standard library is something we just have to deal with. Fortunately, this is only an issue when you have something like a `vector` that takes up more than half of total RAM, and even then only on 32 bit systems. – Baum mit Augen Apr 22 '16 at 19:35

1 Answers1

8

You could write a custom validator, which would be something like:

struct nonnegative {
    unsigned value;
};

void validate(boost::any& v, const std::vector<std::string>& values, nonnegative*, int)
{
    using namespace boost::program_options;
    validators::check_first_occurrence(v);

    std::string const& s = validators::get_single_string(values);
    if (s[0] == '-') {
        throw validation_error(validation_error::invalid_option_value);
    }

    v = lexical_cast<unsigned>(s);
}

And then just use a nonnegative instead of an unsigned.

But I think it'd be easier to just use an int64_t and throw an error if it's negative. Also less code.

Barry
  • 286,269
  • 29
  • 621
  • 977