The trick is to force boost
to classify all numbers as positional values (not to be confused with positional_options_description
. The way you do that is define a style_parser
and give it to the command_line_parser
as an extra_style_parser
:
#include <boost/program_options/option.hpp>
#include <boost/lexical_cast/try_lexical_convert.hpp>
#include <boost/program_options/value_semantic.hpp>
using po = boost::program_options;
std::vector<po::option> ignore_numbers(std::vector<std::string>& args)
{
std::vector<po::option> result;
int pos = 0;
while(!args.empty()) {
const auto& arg = args[0];
double num;
if(boost::conversion::try_lexical_convert(arg, num)) {
result.push_back(po::option());
po::option& opt = result.back();
opt.position_key = pos++;
opt.value.push_back(arg);
opt.original_tokens.push_back(arg);
args.erase(args.begin());
} else {
break;
}
}
return result;
}
Once you have it, this is how you use it:
po::store(po::command_line_parser(argc, argv)
.extra_style_parser(&po::ignore_numbers)
.options(commands)
.run(), vm);
You can now use negative numbers and short command line arguments at the same time.
However, there's still a problem, there's no way to restrict the number of tokens each argument takes, which can be problematic if you use positional arguments. For example, something like this won't work:
foo --coords 1 2 3 4 bar.baz
In order to fix this, we'll need to add a way to force the number of tokens an argument requires:
template<class T, class charT = char>
class bounded_typed_value : public po::typed_value<T, charT>
{
public:
bounded_typed_value(T* store_to)
: typed_value<T, charT>(store_to), m_min(-1), m_max(-1) {}
unsigned min_tokens() const {
if(m_min < 0) {
return po::typed_value<T, charT>::min_tokens();
} else {
return (unsigned)m_min;
}
}
unsigned max_tokens() const {
if(m_max < 0) {
return po::typed_value<T, charT>::max_tokens();
} else {
return (unsigned)m_max;
}
}
bounded_typed_value* min_tokens(unsigned min_tokens)
{
if(min_tokens > 1) {
po::typed_value<T, charT>::multitoken();
}
m_min = min_tokens;
return this;
}
bounded_typed_value* max_tokens(unsigned max_tokens)
{
if(max_tokens > 1) {
po::typed_value<T, charT>::multitoken();
}
m_max = max_tokens;
return this;
}
bounded_typed_value* fixed_tokens(unsigned num_tokens)
{
if(num_tokens > 1) {
po::typed_value<T, charT>::multitoken();
}
m_min = num_tokens;
m_max = num_tokens;
return this;
}
private:
int m_min;
int m_max;
};
template<class T, class charT = char>
bounded_typed_value<T, charT>*
bounded_value()
{
return new bounded_typed_value<T, charT>(0);
}
You can now put it all together like this:
po::positional_options_description p;
p.add("file-name", -1);
boost::program_options::options_description desc;
desc.add_options()
("coords,c", boost::program_options::bounded_value<std::vector<double>>()->fixed_tokens(4), "Bounding box");
po::store(po::command_line_parser(argc, argv)
.extra_style_parser(&po::ignore_numbers)
.positional(p)
.options(commands)
.run(), vm);