Search code examples
c++boost

Boost program_options parsing enum bitmask


My program has a set of recoverable errors that can occur, let's call them ErrorA, ErrorB and ErrorC. For any of these errors, my program can either ignore them (as they are recoverable), or terminate, the latter being the default behaviour. Now I want users to specify if any of these errors should be ignored instead. This should happen through a command line parameter --ignore, which is followed by the names of the errors that shall be ignored. For example:

./MyProgram --ignore ErrorA ErrorC

Within the code, I represent these error categories using a single bitmask enum:

enum class IgnoreErrors {
  None = 0, //Ignore no errors
  ErrorA = 1 << 0,
  ErrorB = 1 << 1,
  ErrorC = 1 << 2
};

I am using boost::program_options and trying to add the --ignore option to the options_description like so:

value<IgnoreErrors>()->multitoken()->default_value(IgnoreErrors::None)

This requires that I provide an implementation of operator>> for IgnoreErrors, and this is where I got stuck. I tried this:

istream& operator>>(istream& in, IgnoreErrors& err) {
  //Assumption: 'in' contains ALL tokens passed to the --ignore flag
  err = IgnoreErrors::None; //Needed to make |= work

  string token;
  while(getline(in, token, ' ')) {
    err |= ignore_error_from_str(token);    
  }

  return in;
}

This correctly parses one or more tokens into a single IgnoreErrors value, however after that my program fails with: the argument ('ErrorA ErrorC') for option '--ignore' is invalid. Interestingly enough, if I do in >> token instead of getline, I only ever get the first token (ErrorA).

If I change the implementation of operator>> to parse a single token into a single IgnoreError value and change my options_description to use value<vector<IgnoreErrors>>(), Boost doesn't recognize the operator>> for IgnoreErrors anymore and instead looks for an implementation for vector<IgnoreErrors>. Which I could provide, however then I have to manually fold this vector into a single IgnoreErrors value, which seems weird. I want to understand what the 'intended' way to handle this situation is.

How can I parse a multitoken argument as a single value using boost::program_options?


Solution

  • So I found a way to make this work that seems reasonable. Instead of providing operator>>, a custom validator can be used, like so:

    void validate(boost::any &v, const std::vector<std::string> &all_tokens,
                  IgnoreErrors *target_type, int) {
      validators::check_first_occurrence(v);
      std::vector<std::string> tokens;
      boost::algorithm::split(tokens,
                              validators::get_single_string(all_tokens),
                              boost::is_any_of(" "));
    
      auto ignore_errors = IgnoreErrors::None;
    
      for (auto &token : tokens) {
        ignore_errors |= ignore_error_from_str(token);
      }
    
      v = boost::any(ignore_errors);
    }
    

    Some things that were not so obvious to me:

    • The result of validators::get_single_string(all_tokens) in my example is "ErrorA ErrorC", so I had to split this string up into multiple tokens manually
    • The specialization of the validate function has to be in the same namespace as the type that it is specialized for (IgnoreErrors in my case)