2

I am working on an application that reads some options through a config file. The code uses boost program_options library to read the options.

The application code has a class that does the following tasks related to reading the option values-

1)function1() - defines all the possible options that are valid. Adds them to an option_description object.
2)function2() - parses the config file and populates the variable_map object.
3)function3() - returns the value of an option.This function looks like this -

template<typename T>
T function3(string optName){
    try{return vm[optName].as<T>();}
    catch(exception& e){ //some error handling message}
}

Now for an option like-

vector_of_string_option=value1,value2,value3

For this I add this option to the options description object as-

("vector_of_string_option", po::value<vector<string>>(), "vector string");

For this vm["vector_of_string_option"].as<vector<string>>() returns a vector with first element - "value1,value2,value3"

I want the returned value to be a vector containing 3 values - {"value1" , "value2" , "value3"}.

Since function3() is a templatized function in the class, I cannot write a specialized function for vector(that would split the string say, using boost::split).

If there is a way I will use the same for vector.

So, is there a way to achieve this inherently through program_options? or any other suggestion to achieve this in my code?

Anil
  • 111
  • 6
  • After answering, I'm at a loss why you go in such detail about `function1`, `function2` and `function3`, You could have just shown the code, and it seems only function3 is relevant. – sehe May 13 '20 at 19:08

1 Answers1

1

The idea for you using Boost Program Options is to use multi-token/composing options.

Let's follow along

1)function1() - defines all the possible options that are valid. Adds them to an option_description object.

auto function1() {
    po::options_description desc;
    for (auto opt : s_opts)
        desc.add_options()(opt, po::value<std::string>());
    desc.add_options()
        ("vector_of_string_option", po::value<VoS>()->multitoken()->composing(), "vector string")
        ;
    return desc;
}

So far so good

2)function2() - parses the config file and populates the variable_map object.

auto function2(std::istream&& is) {
    auto d = function1();
    po::parsed_options parsed = po::parse_config_file(is, d, false);
    po::variables_map vm;
    po::store(parsed, vm);
    po::notify(vm);

    return vm;
}

Still no problems.

3)function3() -

returns the value of an option.This function looks like this -

template <typename T>
T function3(std::string optName, po::variables_map const& vm) {
    try {
        return vm[optName].as<T>();
    } catch (std::exception const& e) {
        std::cerr << "Whoops: " << e.what() << "\n";
        exit(1);
    }
}

Ok.

int main() {
    auto vm = function2(std::istringstream(R"(
bar=BARRRR
# bar=QUXXXX # "cannot be specified more than once"
vector_of_string_option=value1
vector_of_string_option=value2
vector_of_string_option=value3
)"));
    std::cout << function3<std::string>("bar", vm) << "\n";
    for (auto& v : function3<VoS>("vector_of_string_option", vm)) {
        std::cout << " - " << std::quoted(v) << "\n";
    }
}

Prints:

BARRRR
 - "value1"
 - "value2"
 - "value3"

I want the returned value to be a vector containing 3 values - {"value1" , "value2" , "value3"}.

Already done, see it Live On Coliru

Since function3() is a templatized function in the class, I cannot write a specialized function for vector(that would split the string say, using boost::split).

Sure you can! You cannot /partially/ specialize, but you can specialize:

template <>
VoS function3<VoS>(std::string optName, po::variables_map const& vm) {
    try {
        VoS result;
        auto const& raw = vm[optName].as<VoS>();

        using namespace boost::algorithm;
        for(auto& rv : raw) {
            VoS tmp;
            split(tmp, rv, is_any_of(",; "), token_compress_on);
            result.insert(result.end(), tmp.begin(), tmp.end());
        }
        return result;
    } catch (std::exception const& e) {
        std::cerr << "Whoops: " << e.what() << "\n";
        exit(1);
    }
}

That makes it so you can multiple values, but also split each:

int main() {
    auto vm = function2(std::istringstream(R"(
bar=BARRRR
# bar=QUXXXX # "cannot be specified more than once"
vector_of_string_option=value1, value2, value3
vector_of_string_option=value4, value5, value6
)"));
    std::cout << function3<std::string>("bar", vm) << "\n";
    for (auto& v : function3<VoS>("vector_of_string_option", vm)) {
        std::cout << " - " << std::quoted(v) << "\n";
    }
}

Prints

BARRRR
 - "value1"
 - "value2"
 - "value3"
 - "value4"
 - "value5"
 - "value6"

Again, see it Live On Coliru

BONUS TAKES

If you wanted partial specialization, either delegate implemention of function3 to a template class, or use tag dispatch. That would make it possible/easy to parse into set<int> or list<bool> as well.

Draft: http://coliru.stacked-crooked.com/a/7971dd671010d38e

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks for the reply. But, you missed that these functions were defined in a class(I mentioned that in the beginning). So, in your implementation for function3 will not work. Also the variables_map object is a private member of the class. – Anil May 13 '20 at 20:04
  • Why not? That's irrelevant. And also, if you thought it was relevant, that just underlines that you needed to show the code. – sehe May 13 '20 at 20:20
  • (Here's a draft of the idea in "Bonus Take": http://coliru.stacked-crooked.com/a/7971dd671010d38e) – sehe May 13 '20 at 20:22
  • Here's a rework of the answer code to have the class, and the private fields etc.: **[Live On Coliru](http://coliru.stacked-crooked.com/a/c3ca15b9aac934dd)** I don't see a problem - it's actually easier because now you can just overload `function3`, instead of having to specialize [GotW #49, Sutters Mill]()http://www.gotw.ca/publications/mill17.htm. – sehe May 13 '20 at 20:47
  • thanks. Also, for a vector option, I am able to specify it multiple times in the config file even when multtoken() is not used. For other types, when I specify it multiple times in the config file an exception occurs. Is there a way to do the same for VoS options? For now, I am doing this by checking the size() of vector in function3() and in case it is greater than 1 I raise an error. – Anil May 14 '20 at 11:05
  • @BigA Yeah, that surprises me too (**[live test](http://coliru.stacked-crooked.com/a/561664a2532cd402)**). You can indeed check the size of the vector. – sehe May 14 '20 at 11:53