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:Throwing an exceptionAdding a field std::any<std::string> which gets populated by a help messageI 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.
How to report the existence of a `--help` option flag?
c++;error handling
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.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.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.boolparse_cmd_special(int argc, char * * argv, std::ostream& out);// Communicates errors through exceptions.    optionsparse_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.