1

I'm trying to use Boost to be able to pass multiple arguments or have multiple occurrences of a flag with ->multitoken and ->composing. However, I want this to be an optional flag using boost::optional<>.

Below is the basic boost example from their site, modified for my purpose. Without the boost::optional wrapper, everything works as expected.

run //path/to/example:main --letter a b c results in a b c being printed.

If I change options_description to be of boost::optional type:

("letter", po::value<boost::optional<vector<string>>>()->multitoken()->composing(), "multiple values");

Then --letter a b c' Errors with option '--letter' only takes a single argument

And --letter a --letter b Errors with option '--letter' cannot be specified more than once

Anyone know more about boost to be able to use both boost::optional and ->multitoken()/->composing()? I would like to avoid removing boost::optional and adding a default value.

/* The simplest usage of the library.
 */

#include <boost/optional.hpp>
#include <boost/program_options.hpp>
namespace po = boost::program_options;

#include <iostream>
#include <iterator>
using namespace std;

int main(int ac, char* av[]) {
  try {
    po::options_description desc("Allowed options");
    desc.add_options()
    ("help", "produce help message")
    ("letter", po::value<vector<string>>()->multitoken()->composing(), "multiple values");

    po::variables_map vm;
    po::store(po::parse_command_line(ac, av, desc), vm);
    po::notify(vm);

    if (vm.count("help")) {
      cout << desc << "\n";
      return 0;
    }

    if (vm.count("letter")) {
      vector<string> list = vm["letter"].as<vector<string>>();
      for (size_t i = 0; i < list.size(); ++i)
        cout << list[i] << " ";
      cout << '\n';
    }

  } catch (exception& e) {
    cerr << "error: " << e.what() << "\n";
    return 1;
  } catch (...) {
    cerr << "Exception of unknown type!\n";
  }

  return 0;
}
Claire Lee
  • 13
  • 2

1 Answers1

0

Simplicity reduces bugs and improves maintainability.

Simplicity is usually when you reach the "sweet spot" of a library - the way it was designed. In this design, multi-token option are an extension of optionals (an optional is just like container with a maximum size of 1). The design does not expect you to layer both on top of each other.

Instead, I'd leave the code as is and consume how you want to consume it:

boost::optional<strings> dream_variable;
if (vm.contains("letter")) {
    auto& opt = vm.at("letter");
    if (!opt.defaulted()) {
        dream_variable = opt.as<strings>();
    }
}

That's improving readability by

using strings = std::vector<std::string>;

Live Demo

Live On Coliru

#include <boost/optional.hpp>
#include <boost/optional/optional_io.hpp>
#include <boost/program_options.hpp>
namespace po = boost::program_options;

#include <iostream>

int main(int ac, char* av[]) {
    using strings = std::vector<std::string>;
    try {
        po::options_description desc("Allowed options");
        desc.add_options()                   //
            ("help", "produce help message") //
            ("letter",
             po::value<strings>()          //
                 ->multitoken()            //
                 ->composing()             //
                 ->implicit_value({}, ""), //
             "multiple values");

        po::variables_map vm;
        store(parse_command_line(ac, av, desc), vm);
        notify(vm);

        boost::optional<strings> dream_variable;
        if (vm.contains("letter")) {
            auto& opt = vm.at("letter");
            if (!opt.defaulted()) {
                dream_variable = opt.as<strings>();
            }
        }

        if (dream_variable) {
            for (auto& letter : *dream_variable) {
                std::cout << " - " << letter << "\n";
            }
        } else {
            std::cout << " * none\n";
        }
    } catch (std::exception const& e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    } catch (...) {
        std::cerr << "Exception of unknown type!\n";
        return 1;
    }
}

With the output:

+ ./a.out --letter aa bb cc
 - aa
 - bb
 - cc
+ ./a.out --letter
+ ./a.out
 * none

Note I assumed that you wanted implicit_value as well, because it's the only sensible way I can imagine how --letter without value tokens would be different from just not specifying the option.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • If this doesn't give you the control/results you need, you can always refine it using `parsed_options` info: http://coliru.stacked-crooked.com/a/d05aae1b047dfa0c – sehe Oct 25 '22 at 13:29
  • 1
    Solved! Thanks so much for the explanation of the use of optional, and how that it doesn't exactly make sense with the use of another container. The extensive examples were very helpful too! – Claire Lee Oct 25 '22 at 17:28