1

I have a small function which parses my command line arguments. It also tests for required options and throws on any errors. The function currently takes argc and argv and returns a structure which is formed in this way:

struct options
{
  std::string arg1;
  std::uint16_t arg2;
  std::any<std::string> not_required;
};

Any parser errors are reported by exceptions.

Now i did want to add a --help switch. But how do i implement it correctly? I have thought of 3 possible ways, namely:

  1. Throwing an exception
  2. Adding a field std::any<std::string> which gets populated by a help message

I don't like the first option because of the use of exceptions as a method of control flow and the second option has the problem, that i would need to mark each field as optional because of the nature of the --help switch, which is allowed to be a stand alone option.

  • Does your function returns one structure (and arg1 and arg2 are values for mandatory parameters ?) or do you return a list/vector/set of several structures (and arg1 and arg2 are name an value of the parameter) ? Do you have a table of all possible options (for example used in your parser) ? Last but not least, can you please explain the 3rd possible way (that doesn't appear in your numbered list) ? – Christophe Sep 21 '16 at 20:55
1

I think that the most straight-forward way to solve this is to have the command-line argument parsing function (let's call it parse_cmd_args) handle the --help option itself. In the most simple implementation, it would just print the help message and then call exit(0).

Of course, this is not a very clean separation of concerns and hard to unit-test so you might want to do better.

First, instead of printing directly to standard output, you can make parse_cmd_args accept an additional argument of type std::ostream and use it to display the help text. In your unit test, you can pass a std::ostringstream instead of std::cout.

The next thing we have to get rid of is calling exit directly from within parse_cmd_args. As I see it, we have three possible cases.

  1. Parsing the command-line arguments succeeds and the parsed values are stored in an options object for further use by the program.
  2. Parsing the command-line options fails. The program should print an error message and immediately exit with a status indicating failure.
  3. The --help (or --version) option was encountered. The program should print the corresponding text and then immediately exit with a status indicating success.

You are currently handling case 1 through normal return, case 2 via throwing an exception and are undecided for case 3. I agree that handling case 3 with an exception is not the most elegant way (although I would not object against it too strongly). On the other hand, I think that cases 2 and 3 are quite similar and should be handled similarly.

As “we” have already decided to make parse_cmd_args print the help text in case 3, we could also make it print the error message in case 2. Since the error message should go to standard error output, we should add a second std::ostream argument for that.

Now that parse_cmd_args handles all the printing internally, all it has to communicate to its caller is whether to exit immediately and if so, with what status. Since exiting immediately and having parsed options are orthogonal to each other, you could use a variant for this.

std::variant<options, int>
parse_cmd_args(int argc, char * * argv, std::ostream& out, std::ostream& err);

Unfortunately, std::variant will only be available in C++17 so you might have to use boost::variant for the time being. If this seems overkill, there is always the pragmatic (if maybe less elegant) option to use options as an output parameter and return the exit status, inventing some convention (such as returning a negative value) to indicate “don't exit”.

A second thought is to handle the --help option in a separate function (let's call it parse_cmd_special) that main calls before calling parse_cmd_args. You would then end up with something like this.

// Returns true if and only if the help message was printed and the program
// should exit without any further argument processing.
bool
parse_cmd_special(int argc, char * * argv, std::ostream& out);

// Communicates errors through exceptions.    
options
parse_cmd_args(int argc, char * * argv);

As a final remark, the first thing I like to do in my main functions is to pack all command-line arguments from argv[1] to argv[argc - 1] into a std::vector<std::string> and pass that around for any further processing. Unless you have literally thousands of command-line arguments, the overhead should not be noticeable and your function interfaces (consider the three shown above) become much cleaner.

  • Even if you have thousands of command line arguments, just pass the args vector by const reference – Caleth Sep 22 '16 at 8:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.