This question has come up before, but it seems that none of the answers provide alternatives with boost
-style generic programming.
Like many I use boost:program_options to parse command line line options. My current project is a program to manipulate data (e.g. images) with operators whose order is not interchangeable, e.g.
add 3 then multiply by 2
$ manipulate -in someimage.tif -a 3 -m 2
is not generally the same as
multiply by 2 then add 3
$ manipulate -in someimage.tif -m 2 -a 3
The -in
option loads the file contents into a vector current_image
, and each option on the command line modifies current_image
.
But the variable_map
container does not retain the order in which options are added. Not explicitly, at least. The answer in this post comes closest to what I have in mind, but then the amount of extra parsing code needed is about the same as with getopt()
.
Does anyone know a way to store the order of program options in the boost-provided container? Is it fundamentally impossible? Or is it possible (maybe even implemented) with a sequence container?
EDIT 1 I did find this really old post which seems still valid, stating that yes you can just iterate over a variables_map. Of course the order is not actually specified to be the same as the order on the command line (left to the compiler writers), so I guess it does still classify as a hack.
EDIT 2 That is not enough as the options are sorted by option string so the iteration order is not the same as the insertion order.
Actually what you have there is more akin to an expression grammar. I'd suggest writing a grammar/parser for that instead of (ab?)using program_options for this.
An example:
Live On Coliru
// #define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted/struct.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
struct Operation {
enum Kind { add, multiply } kind;
double operand;
friend std::ostream& operator<<(std::ostream& os, Kind k) {
switch (k) {
case add: return os << "--add";
case multiply: return os << "--multiply";
};
return os << "??";
}
};
BOOST_FUSION_ADAPT_STRUCT(Operation, (Operation::Kind,kind)(double,operand))
template <typename It, typename Skipper = qi::blank_type>
struct expression_grammar : qi::grammar<It, std::vector<Operation>(), Skipper> {
expression_grammar() : expression_grammar::base_type(start) {
using namespace qi;
opkinds.add
("-a", Operation::add)
("--add", Operation::add)
("-m", Operation::multiply)
("--multiply", Operation::multiply)
;
option = opkinds > eol > double_;
start = *(option > eol);
BOOST_SPIRIT_DEBUG_NODES((start)(option))
}
private:
qi::symbols<char, Operation::Kind> opkinds;
qi::rule<It, Operation(), Skipper> option;
qi::rule<It, std::vector<Operation>(), Skipper> start;
};
int main(int argc, char const** argv) {
std::stringstream iss;
if (argc)
std::copy(argv+1, argv+argc, std::ostream_iterator<const char*>(iss, "\n"));
typedef boost::spirit::istream_iterator It;
expression_grammar<It> grammar;
It first(iss >> std::noskipws), last;
std::vector<Operation> operations;
bool ok = qi::phrase_parse(first, last, grammar, qi::blank, operations);
if (ok)
{
std::cout << "Parse success\n";
for (auto const& op : operations)
std::cout << boost::fusion::as_vector(op) << "\n";
} else
std::cout << "Parse failed\n";
if (first!=last)
std::cout << "Remaining input: '" << std::string(first,last) << "'\n";
}
Note
- I choose, gratuitously, to use
eol
as the option separator. You might want to use '\0'
instead. This was easiest because the blank
skipper already skips whitespace /except/ eol
. I'm lazy :)
You might wnat to mix-and-match (not treat all parameters as part of the expression). A common pattern would be
myprogram -x option1 -v -o filename -- my expression grammar follows
A common alternative pattern is to make the expression a single parameter:
myprogram -e 'add 5; multiply 32;' -x option1
See this approach Live on Coliru too
I was lazy again with the "Parse success" printing (I didn't want to implement operator<<
for the Operation
type)
enable debug info with #define BOOST_SPIRIT_DEBUG
(uncomment the first line)
<start>
<try>-a\n8\n-m\n7\n-a\n32\n</try>
<option>
<try>-a\n8\n-m\n7\n-a\n32\n</try>
<success>\n-m\n7\n-a\n32\n</success>
<attributes>[[--add, 8]]</attributes>
</option>
<option>
<try>-m\n7\n-a\n32\n</try>
<success>\n-a\n32\n</success>
<attributes>[[--multiply, 7]]</attributes>
</option>
<option>
<try>-a\n32\n</try>
<success>\n</success>
<attributes>[[--add, 32]]</attributes>
</option>
<option>
<try></try>
<fail/>
</option>
<success></success>
<attributes>[[[--add, 8], [--multiply, 7], [--add, 32]]]</attributes>
</start>
Parse success
(--add 8)
(--multiply 7)
(--add 32)