c++loopsvectorboostboost-program-options

Iterating over vectors created by Boost's program options in C++


I'm new to C++ and trying to use Boost's program_options header to create a vector<string> of positional options, but no matter what I do, I can't seem to actually do anything or iterate over them. I'm currently compiling to C++23 on GCC 14.1.1.

I'm currently using this for the options:

po::options_description desc("Testing");
desc.add_options()
("help", "produce help message")
("path,p", po::value< vector<string> >(), 
 "Path of directory/file");

po::positional_options_description p;
p.add("path", -1);

po::variables_map vm;
po::store(po::command_line_parser(argc, argv).
      options(desc).positional(p).run(), vm);
po::notify(vm);

I tried iterating over it with a for loop like this, I get a LSP (clangd) error and my program doesn't compile:

if (vm.count("path")) {
  for (auto i : vm.count("path")) {
    cout << "Paths are: " 
     << i << endl;
  }
}

clangd error: Invalid range expression of type 'std::map<std::basic_string<char>, boost::program_options::variable_value>::size_type' (aka 'unsigned long'); no viable 'begin' function available

GCC error:

/home/main/projects/cpp-testing/src/new.cpp: In function ‘int main(int, char**)’:
/home/main/projects/cpp-testing/src/new.cpp:26:34: error: ‘begin’ was not declared in this scope
   26 |     for (auto i : vm.count("path")) {
      |                                  ^
/home/main/projects/cpp-testing/src/new.cpp:26:34: note: suggested alternatives:
In file included from /usr/include/c++/14.1.1/string:53,
                 from /usr/include/c++/14.1.1/bits/locale_classes.h:40,
                 from /usr/include/c++/14.1.1/bits/ios_base.h:41,
                 from /usr/include/c++/14.1.1/ios:44,
                 from /usr/include/c++/14.1.1/ostream:40,
                 from /usr/include/c++/14.1.1/iostream:41,
                 from /home/main/projects/cpp-testing/src/new.cpp:1:
/usr/include/c++/14.1.1/bits/range_access.h:114:37: note:   ‘std::begin’
  114 |   template<typename _Tp> const _Tp* begin(const valarray<_Tp>&) noexcept;
      |                                     ^~~~~
In file included from /usr/include/c++/14.1.1/string_view:56,
                 from /usr/include/c++/14.1.1/bits/basic_string.h:47,
                 from /usr/include/c++/14.1.1/string:54:
/usr/include/c++/14.1.1/bits/ranges_base.h:487:47: note:   ‘std::ranges::_Cpo::begin’
  487 |     inline constexpr ranges::__access::_Begin begin{};
      |                                               ^~~~~
In file included from /usr/include/boost/range/functions.hpp:18,
                 from /usr/include/boost/range/iterator_range_core.hpp:38,
                 from /usr/include/boost/lexical_cast.hpp:48,
                 from /usr/include/boost/program_options/value_semantic.hpp:14,
                 from /usr/include/boost/program_options/options_description.hpp:13,
                 from /usr/include/boost/program_options.hpp:15,
                 from /home/main/projects/cpp-testing/src/new.cpp:3:
/usr/include/boost/range/begin.hpp:110:61: note:   ‘boost::range_adl_barrier::begin’
  110 | inline BOOST_DEDUCED_TYPENAME range_iterator<const T>::type begin( const T& r )
      |                                                             ^~~~~
In file included from /usr/include/c++/14.1.1/bits/stl_iterator_base_types.h:71,
                 from /usr/include/c++/14.1.1/bits/stl_construct.h:61,
                 from /usr/include/c++/14.1.1/bits/char_traits.h:57,
                 from /usr/include/c++/14.1.1/ios:42:
/usr/include/c++/14.1.1/bits/iterator_concepts.h:983:10: note:   ‘std::ranges::__access::begin’
  983 |     void begin() = delete;
      |          ^~~~~
/home/main/projects/cpp-testing/src/new.cpp:26:34: error: ‘end’ was not declared in this scope
   26 |     for (auto i : vm.count("path")) {
      |                                  ^
/home/main/projects/cpp-testing/src/new.cpp:26:34: note: suggested alternatives:
/usr/include/c++/14.1.1/bits/range_access.h:116:37: note:   ‘std::end’
  116 |   template<typename _Tp> const _Tp* end(const valarray<_Tp>&) noexcept;
      |                                     ^~~
/usr/include/c++/14.1.1/bits/ranges_base.h:488:45: note:   ‘std::ranges::_Cpo::end’
  488 |     inline constexpr ranges::__access::_End end{};
      |                                             ^~~
In file included from /usr/include/boost/range/functions.hpp:19:
/usr/include/boost/range/end.hpp:104:61: note:   ‘boost::range_adl_barrier::end’
  104 | inline BOOST_DEDUCED_TYPENAME range_iterator<const T>::type end( const T& r )
      |                                                             ^~~
/usr/include/c++/14.1.1/bits/ranges_base.h:138:10: note:   ‘std::ranges::__access::end’
  138 |     void end() = delete;
      |          ^~~

When I try to create a new vector<string> with the argument instead, I also get an error about conversion from std::map<std::basic_string<char>, boost::program_options::variable_value>::size_type.

vector<string> paths = vm.count("path");

clangd error: No viable conversion from 'std::map<std::basic_string<char>, boost::program_options::variable_value>::size_type' (aka 'unsigned long') to 'vector<std::string>' (aka 'vector<basic_string<char>>)

GCC error:

/home/main/projects/cpp-testing/src/new.cpp: In function ‘int main(int, char**)’:
/home/main/projects/cpp-testing/src/new.cpp:25:34: error: conversion from ‘std::map<std::__cxx11::basic_string<char>, boost::program_options::variable_value>::size_type’ {aka ‘long unsigned int’} to non-scalar type ‘std::vector<std::__cxx11::basic_string<char> >’ requested
   25 |   vector<string> paths = vm.count("path");
      |                          ~~~~~~~~^~~~~~~~

Also, my question was marked as a duplicate, linking this one. My question is specifically about vectors, and the one linked isn't. The answers given in that thread don't appear to have any obvious way to adapt them to what I want to do, unless I'm missing something here (which I won't deny the possibility of, considering that I have next to no idea what I'm doing with this language).


Solution

  • Your problem isn't so much iterating, but accessing the vector. The variable-map contains boost::any objects.

    You can get the actual value from the map as follows:

    if (vm.count("path")) {
        auto& v = vm["path"].as<std::vector<std::string>>();
    

    After that you can iterate it as you were trying to:

        for (auto& s : v)
            std::cout << "Path: " << s << std::endl;
    

    Live On Coliru

    #include <boost/program_options.hpp>
    #include <iostream>
    
    namespace po = boost::program_options;
    
    int main(int argc, char* argv[]) {
        po::options_description desc("Testing");
        desc.add_options()                                    //
            ("help", "produce help message")                  //
            ("path,p", po::value<std::vector<std::string>>(), //
             "Path of directory/file")                        //
            ;
    
        po::positional_options_description p;
        p.add("path", -1);
    
        po::variables_map vm;
        po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
        po::notify(vm);
    
        if (vm.count("help")) {
            std::cout << desc << std::endl;
            return 1;
        }
    
        if (vm.count("path")) {
            auto& v = vm["path"].as<std::vector<std::string>>();
            for (auto& s : v)
                std::cout << "Path: " << s << std::endl;
        } else {
            std::cout << "Path not set" << std::endl;
        }
    }
    

    Prints:

    g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lboost_{program_options,filesystem}
     ./a.out -p a -p b --path "c and d"
    
    Path: a    
    Path: b    
    Path: c and d