Martin
Martin

Reputation: 195

boost::program_options bool_switch used multiple times

What I currently have is

("someOption,s", po::bool_switch(&variable)->default_value(false), "")

I would like to be able to specify the argument multiple times and each occurence would switch the value of this bool.

Examples:

default value = false

./program -s
value == true

./program -s -s
value == false

./program -s -s -s
value == true

Is there a way to use something like bool_switch multiple times so switch on/off repeatedly? Do I need my custom type and validator for that?

Maybe I can somehow allow specifying the option multiple times and then do vm.count("someOption") and based on its value (even/odd) switch my variable. But I would prefer to specify that behaviour right in the options descripton (add_options) without checking and modifying the values later.

Upvotes: 2

Views: 1268

Answers (2)

Martin
Martin

Reputation: 195

So this is as close I got so far. It does not look very elegant, more like a workaround, I would be happy to know better ("shorter") solution. There could be also some problems with the code, feel free to correct me please. Currently it works only if default is treated as false. I don't know how to start with true, it would require to know the default value somehow in validate function. It is out of scope of this questions and hopefully solved here: boost::program_options custom validate and default value

// my custom class
class BoolOption {
public:
    BoolOption(bool initialState = false) : state(initialState) {}
    bool getState() const {return state;}
    void switchState() {state = !state;}
private:
    bool state;
};

// two variables
BoolOption test1;
BoolOption test2;

// validate
void validate(boost::any &v, std::vector<std::string> const &xs, BoolOption*, long)
{
    if (v.empty()) {
        v = BoolOption(true); // I don't know how to assign default here so this works only when default is false
    } else {
        boost::any_cast<BoolOption&>(v).switchState();
    }
}

optionsDescription->add_options()
        ("test1,t", po::value<BoolOption>(&test1)->default_value(BoolOption(true), "true")->zero_tokens(), "")
        ("test2,T", po::value<BoolOption>(&test2)->default_value(BoolOption(false), "false")->zero_tokens(), "")
;

// output result
cout << test1.getState() << endl;
cout << test2.getState() << endl;

This seems to work correctly, I can call it in a way:

./program -t -t -T -t -T

and it switches the arguments.

Upvotes: 1

sehe
sehe

Reputation: 393174

I've literally spent 70 minutes trying to get this to work using

  • composing() to allow repeats
  • vector valued dummy options with implicit_value({}, "")custom notifiers (they only run once)
  • custom notifier() - they only run once regardless of how often the option is present and successfully parsed
  • getting a vector-valued option's size from the variables_map after store/notify. Sadly, the size is always 1, presumably because the "compose" doesn't actually compose within a single store operation (it only composes between several runs, so different sources of options, then?).

The sad conclusion is, there doesn't appear to be a way. Here, my usual mantra is confirmed: "Simplicity Trumps Omniscient Design", and I'd suggest doing the same but using https://github.com/adishavit/argh:

Live On Coliru

#include "argh.h"
#include <iostream>

namespace {
    template <typename It>
    size_t size(std::pair<It, It> const& range) { return std::distance(range.first, range.second); }
}

int main(int argc, char** argv) {
    argh::parser p(argc, argv);

    auto num_s = size(p.flags().equal_range("s"));
    bool const variable = num_s % 2;
    std::cout << "Repeated: " << num_s << ", effective " << std::boolalpha << variable;

    std::cout << " (Command line was:";
    while (*argv) std::cout << " " << *argv++;
    std::cout << ")\n";
}

When run with various commandlines, prints:

Repeated: 0, effective false (Command line was: ../build/sotest)
Repeated: 1, effective true (Command line was: ../build/sotest -s)
Repeated: 2, effective false (Command line was: ../build/sotest -s -s)
Repeated: 3, effective true (Command line was: ../build/sotest -s -s -s)
Repeated: 4, effective false (Command line was: ../build/sotest -s -s -s -s)
Repeated: 3, effective true (Command line was: ../build/sotest -s -s -s --other bogus)

Upvotes: 4

Related Questions