program options from command line initialize
I tried boost::program_options but simply didn't like it.

boost::program_options has the feature that certain options are additive or hidden - which is why their usage requires so much extra work (you have to specify each option you use and state how they are processed). If you are fine without these features, you can work with config files - *.ini format - and simply read and store all option that the file has, no need for the whole mess in the main. It is not hard to implement a config file reader and Windows has built in functions for reading arguments from a .ini file.

Also this way you can supply input parameters via an .ini file instead of command line, which is much more convenient if you have more than 5 parameters and I tend to have dozens in tests.

In my case, I implemented a class ConfigFile that basically wraps a std::map&lt;string,string&gt; whose keys are in format SECTION/NAME; with added functions for getting/setting int/double/string values as well as functionality for reading a whole ini file.

In case you want to read a command line via the ConfigFile class: you can reinterpret cmd arguments and store them inside the ConfigFile class. string-based options simply store as is or optionally add a prefix-section. To support index based options you ought to provide a vector of strings to that interprets them.

I don't know of any open-source library that implements something like this, though I didn't check for any. But it is easy to implement on your own. I might publish my implementation together with a couple of other utility features as an open-source but not now. So...

Example of CMD parsing:

class CConfig
{
public:
            // sKeys is used to store index based inputs of CMD
    void ParseCMD(  int                         argc,
            const   char**                      argv,
                    std::vector&lt;std::string&gt;    sKeys = {});

   std::string  GetString(std::string key,  std::string  defaultValue);
   double       GetDouble(std::string key,  double       defaultValue);
   int          GetInt(   std::string key,  int          defaultValue);

   void         SetString(std::string key,  std::string  value);
   void         SetDouble(std::string key,  double       value);
   void         SetInt(   std::string key,  int          value);

private:
    std::mutex                          m_mutex;
    std::map&lt;std::string, std::string&gt;  m_map;
};

void CConfig::ParseCMD( int                         argc,
                const   char**                      argv,
                        std::vector&lt;std::string&gt;    sKeys)
    {
        std::lock_guard&lt;std::mutex&gt; g(m_mutex);
        size_t index = 0;
        for (int i = 1; i &lt; argc; i++)
        {
            if (argv[i][0] != '-')
            {
                if (sKeys.size() &lt;= index)
                {
                    std::cout "\n[warning]: unassigned input parameters; total number of index-based input parameters is: " &lt;&lt; sKeys.size();
                    continue;
                }

                m_map[sKeys[index]] = argv[i][0];
                continue;
            }

            if (i + 1 &lt; argc &amp;&amp; argv[i + 1][0] != '-')
            {
                m_map[argv[i]+1] = argv[i + 1];
                i++;
            }
            else
            {
                // simply defines this key with empty value
                m_map[argv[i]+1] = {};
            }
        }

        if (sKeys.size() &gt; index)
        {
            // or add option for making it into an error and not just a warning print
            std::cout &lt;&lt; "\n[warning]: not all index-based values were assigned");
        }
    }

// in main simply write:
   int main(int argc, const char* argv[])
   {
         CConfig config;
         config.ParseCMD(argc, argv, {/*list of strings for identification*/});
         ...
   }