boost::program_options custom validate and default

2019-08-25 02:22发布

问题:

I am using boost::program_options to parse arguments. Because I cannot break compatibility I need to allow specifying some arguments multiple times. I need to do it for example for strings (last one wins) or for booleans (every occurence switches the value).

Let's show what I have on bool (for strings it should be easier because it does not matter what the default is when parameter is used, because it is overwritten with new value). I have my own class BoolValue and custom validate function which switches the value with every occurence. So if you have variable with value false and call program like this

./program -t -t -t

it switches it to true, false, and true again.

I can achieve it with following code and it works ok if default value is false. But sometimes I need the default value to be true (so the example above would switch it to false, true and finally false again).

I can make two classes like TrueValue and FalseValue of course but it does not look nicely. So - can I somehow read the specified default_value inside validate function for initial assignement when still empty?

// 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 test 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); // here is the problem
        // it works when default false only (of course)
        // I don't know how to read the default_value here
    } 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;

As stated above very awkward solution would be to have two classes for that purpose, can be done also with template, something like

template <bool B> class SwitchOption {
public:
    SwitchOption() : value(B) {}
    bool getValue() const {return value;}
    void setValue(bool value) {this->value = value;}
    void switchState() {value = !value;}
    string toStr() {return value ? "true" : "false";}
private:
    bool value;
};

// so validate as
template <bool B> void validate(boost::any &v, vector<string> const &xs __unused, SwitchOption<B>*, long)
{
    if (v.empty()) {
        v = SwitchOption<B>();
    }
    boost::any_cast<SwitchOption<B>&>(v).switchState();
}

// and add_options like this:
("switch,s", boost::program_options::value<SwitchOption<true> >(&variableName)->default_value(SwitchOption<true>(), "true")->zero_tokens(), "")
// can be done with macro, but still I have to specify "true" here in add_options and also when defying the variable itself as
// SwitchOption<true> variableName;
// which is bad duplication

Lost in too complicated solutions :-)

回答1:

Used as in original without default value (always false) and switched after options parsing done for those variables where default should be true.