2

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.

Martin
  • 165
  • 1
  • 15

2 Answers2

4

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)
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you very much and I am sorry for your 70 minutes ;-) I have to stay with boost as we are using this options library for other "more complicated" things. I was also trying to get deeper into it meanwhile and currently did some unexpected little progress. I can use combination of zero_tokens(), my custom struct type and custom validate function to allow multiple "-s" and to count the number of repeats, then I can add some retype of my struct to bool or something like that and it will work. But it looks quite complicated, but maybe only way... – Martin Aug 07 '18 at 13:38
  • @Martin good luck, if you do have a workaround, please share your own answer. My [position is here on twitter](https://twitter.com/sehetw/status/1026816375485026305). I'd be surprised if you can make it work without extra assumptions (e.g. the flags must be consecutive) or ugly hacks like shared global state (which again will hinge on assumptions about the implementation details of the parser(s)) – sehe Aug 07 '18 at 13:40
  • Sure I will share if I succeed. – Martin Aug 07 '18 at 13:44
  • I added some "workaround". Please feel free to check it and let me know what you think. Few things missing yet and looks too complicated, but could work maybe. – Martin Aug 07 '18 at 14:21
  • You should post that as an anwer, I suppose – sehe Aug 07 '18 at 14:21
  • Thanks. I posted as answer now even though it is not as elegant as I was hoping for. Do you know if I can read the default_value of the option inside the validate option so I could know if the variable start as true or false? I could use different class for it, but it would be better to get the default somehow. – Martin Aug 08 '18 at 07:16
  • I think the default value is out of scope of this question, I will create another one. – Martin Aug 08 '18 at 08:10
  • https://stackoverflow.com/questions/51741948/boostprogram-options-custom-validate-and-default-value – Martin Aug 08 '18 at 08:27
1

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.

Martin
  • 165
  • 1
  • 15