Search code examples
c++boostboost-program-options

Handle no value option in Config file in Boost program_options


This question has been already been asked here before - Boost parse_config_file, empty key value. But since there was no proper solution provided there, I am asking this again hoping someone could provide a better solution.

Unlike the above question, In my case, the code is not throwing the Boost Error:

boost::program_options::invalid_option_value

Instead, for string options, it sets the value to an empty string("") and for bool, it is set to true. The intention of my code is to identify whether some option is set in the config file or not. I was hoping to test this using vm[optionName].count() (where vm is the variables_map object) but in the case where the value is not specified like option=, this returns true and hence cannot be used.

I also tried - vm[optionName].defaulted() , vm[optionName].empty() and implicit_value() and default_value() while adding the options but none of them worked.


Solution

  • UPDATE Initial answer got the wrong idea. Here's the update.

    So you want foo= (without a value) to behave as if the line wasn't even in the config.

    That means that the default value semantics (i.e. what happens when notifying - which migrates state from the Parser Component to the Storage Component) aren't good.

    You could pave over it by inventing your own value-semantics (mybool_switch, so to speak) and or settling for a value<my_particulat_bool> where you add streaming operations so that the option behaves the way you want. In other words, using a canon to shoot a fly.

    However, by far the simpler option would be to interfere at the parser stage, changing the parsed_options before you notify().

    Here's a rather complete illustration with a live demo:

    Live On Coliru

    #include <boost/program_options/config.hpp>
    #include <boost/program_options.hpp>
    #include <iostream>
    #include <iomanip>
    
    namespace po = boost::program_options;
    
    int main() {
        po::options_description desc;
        desc.add_options()
            ("foo", po::bool_switch())
            ("bar", po::bool_switch()->default_value(false))
            ("qux", po::bool_switch()->implicit_value(false))
            ;
    
        std::set<std::string> const bool_switches {"foo", "bar", "qux" };
    
        for (std::string contents :
                { "", "foo=", "foo=true", 
                      "bar=", "bar=true", 
                      "qux=", "qux=true"})
        {
            std::istringstream iss(contents);
            po::parsed_options parsed = po::parse_config_file(iss, desc, false);
    
            std::cout << "\n---\n" << std::quoted(contents) << "\n";
    
            // the magic is here:
            for (auto it = parsed.options.begin(); it!= parsed.options.end();) {
                using V = std::vector<std::string>;
                V const& v = it->value;
                if (bool_switches.count(it->string_key) && (v==V{} || v==V{""})) {
                    std::cout << "*** Discarding config key without a value: " << it->string_key << "\n";
                    it = parsed.options.erase(it);
                } else {
                    ++it;
                }
            }
    
            po::variables_map vm;
            po::store(parsed, vm);
    
            for (auto& key : bool_switches) {
                auto& entry = vm[key];
                std::cout << " " << key << " ->" << std::boolalpha
                    << (entry.empty()?" .empty()":"")    
                    << (entry.defaulted()?" .defaulted()":"");
                if (entry.empty())
                    std::cout << " (no value)\n";
                else
                    std::cout << " value:" << entry.as<bool>() << "\n";
            }
        }
    }
    

    Which will print

    ---
    ""
     bar -> .defaulted() value:false
     foo -> .defaulted() value:false
     qux -> .defaulted() value:false
    
    ---
    "foo="
    *** Discarding config key without a value: foo
     bar -> .defaulted() value:false
     foo -> .defaulted() value:false
     qux -> .defaulted() value:false
    
    ---
    "foo=true"
     bar -> .defaulted() value:false
     foo -> value:true
     qux -> .defaulted() value:false
    
    ---
    "bar="
    *** Discarding config key without a value: bar
     bar -> .defaulted() value:false
     foo -> .defaulted() value:false
     qux -> .defaulted() value:false
    
    ---
    "bar=true"
     bar -> value:true
     foo -> .defaulted() value:false
     qux -> .defaulted() value:false
    
    ---
    "qux="
    *** Discarding config key without a value: qux
     bar -> .defaulted() value:false
     foo -> .defaulted() value:false
     qux -> .defaulted() value:false
    
    ---
    "qux=true"
     bar -> .defaulted() value:false
     foo -> .defaulted() value:false
     qux -> value:true