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
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
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.
- Parsing the command-line arguments succeeds and the parsed values are stored in an
options object for further use by the program.
- Parsing the command-line options fails. The program should print an error message and immediately exit with a status indicating failure.
--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.
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.
parse_cmd_args(int argc, char * * argv, std::ostream& out, std::ostream& err);
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
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.
parse_cmd_special(int argc, char * * argv, std::ostream& out);
// Communicates errors through exceptions.
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[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.