Search code examples
c++boostboost-program-options

Split value for vector<string> option in boost program_options


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?


Solution

  • 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