3

Following this post, where I have found a temporary workaround for my other problem, I want to know if I can replace the int argc, char** argv with a std::vector<std::string> variable/object.

Consider the imaginary code:

#include <iostream>
#include <CloseLibrary>

void someFunction(int argc, char** argv){
    for (int i = 0; i < argc; ++i) {
        std::cout << argv[i] << std::endl;
    }
}

int myFunc(int argc, char** argv){
    someFunction(argc, argv);

    return 0;
}

where the CloseLibrary is a closed library that I don't have access to the source code, and the someFunction function from that library demands the int argc, char** argv command line arguments. But for some reason I can't have double pointers char** in my code.

Here in this post something like what I need is proposed, but I don't know how to use that. Can I write the code this way:

#include <iostream>
#include <CloseLibrary>
#include <vector>

void someFunction(int argc, char** argv){
    for (int i = 0; i < argc; ++i) {
        std::cout << argv[i] << std::endl;
    }
}

int myFunc("args", [](std::vector<std::string> args){
    std::vector<char *> cstrs;
    cstrs.reserve(args.size());
    for (auto &s : args) cstrs.push_back(const_cast<char *>(s.c_str()));
    someFunction(cstrs.size(), cstrs.data());

    return 0;
}

Or maybe there is a more canonical way to do this? I would appreciate it if you could help me find the correct way to do this and understand the solution. Thanks for your help in advance.

P.S.1. The char* argv[] method is ok in the body of the function but not ok in the inputs. I don't know why pybind11 does this!

P.S.2. Here on pybind11 gitter, this was suggested:

void run(const std::vector<std::string>& args) {
    for(auto&& e : args) std::cout << e << '\n';
}

P.S.3. Also suggested on pybind11 Gitter:

char** argv = new char*[vec.size()]; // just like malloc(sizeof(char*)*vec.size());
for (int i = 0; i < vec.size(), i++) {
    argv[i] = new char[vec[i].size()];
    memcpy(argv[i], vec[i].data(), vec[i].size()); // or strcpy
}
Foad S. Farimani
  • 12,396
  • 15
  • 78
  • 193
  • 5
    What about std::vector args( argv, argv + argc ); ? – André Feb 03 '20 at 13:57
  • @André that should be fine I guess. Would please elaborate in a post? – Foad S. Farimani Feb 03 '20 at 13:58
  • I may have misunderstood your question. Do you want to "simply" iterate through your args with std::vector args( argv, argv+argc )? Or would you like to do the reverse: Build up a char** argv, int argc from a given std::vector< std::string > ? – André Feb 03 '20 at 19:45
  • @André my question is two-fold. 1. how to get the second argument of the command line arguments in any form other than `char** argv` or `char* arg[]`. I initially thought I could use some form of `std::vector` but I'm not sure if that's a possibility. maybe `char* argv_` or `void* argv_`. 2. how to convert/pars that second argument to the form of `char* argv[]` as this is possible in the body of the function. – Foad S. Farimani Feb 03 '20 at 19:53
  • @Foad It isn't clear what you are asking exactly. Please detail the exact signature of the functions you need to call and why you have those restrictions. – Acorn Feb 03 '20 at 20:00
  • @Acorn the exact functions are huge. I had tried to put a concise representation in [this post](https://stackoverflow.com/q/60038424/4999991). Also the reason why `char** var` and `char* var[]` are not allowed are due to pybind11 limitations as mentioned [here](https://github.com/pybind/pybind11/issues/417#issuecomment-248150544). I had linked to all these references in the original post. Also, the problem is unfolding upon me the further I research a,nd I try to add the new info in **P.S.**es. – Foad S. Farimani Feb 03 '20 at 20:05

2 Answers2

8

You could use the constructor which initializes the vector from a given range, with the argv parameter acts as the starting iterator and argv+argc acting as the ending iterator.

For example, I usually start my main function with:

int main( int argc, char* argv[] )
{
    std::vector< std::string > args( argv, argv + argc );

    for ( auto s : args )
    {
        std::cout << s << std::endl;
    }
}

Note that this will also capture the first argument (argv[0]) which usually (but not necessarily) hold the name of the application when it is started.

In your case, you would like to do the reverse, build up a contiguous array of char* from a std::vector< std::string >. I would do something like:

std::vector< char* > rargs( args.size(), 0 ); // Initialize N nullptrs.
for ( int i=0; i<args.size(); ++i )
{
    std::strcpy( rargs[i], args[i].c_str() ); // One-by-one strcpy them 
}

And then you can pass them into a function accepting an argc, argv as

someFunction( rargs.size(), rargs.data() );
André
  • 18,348
  • 6
  • 60
  • 74
  • Might be worth noting that this is the iterator constructor of `std::vector`. The `argv` parameter acts as the "begin" iterator, pointing at the first item in the array of `char*`, while `argv + argc` acts like the "end" iterator, pointing one past the end in the array of `char*`. – Arthur Tacca Feb 03 '20 at 14:10
  • hold on, the `char* argv[]` is also not allowed as an input to the main function. :( – Foad S. Farimani Feb 03 '20 at 14:31
  • I also had to feed the `int argc, char** argv` to the `someFunction`. That I can't change! – Foad S. Farimani Feb 03 '20 at 14:38
  • 1
    @Foad In the context of a function argument, `char**` is exactly equivalent to `char*[]`. Feel free to use whichever one you prefer, the code in this answer will still work. – Arthur Tacca Feb 03 '20 at 16:30
  • 2
    @ArthurTacca: Actually, `char*[]` "degrades" to `char**`. You will *always* be passing a pointer, and *never* an array. That distinction is important, because in the called function you cannot determine the length of the array with `sizeof` (as you're seeing a pointer, not an array). That is why the standard made *double* sure you get `argv` right -- `argc` tells you its size *and* it's zero-terminated. – DevSolar Feb 04 '20 at 10:45
  • @DevSolar I was thinking as mathematician, where saying "x is equivalent to y" is the same as saying "y is equivalent to x". But now I look back I agree I made it sound the wrong way round, thanks for pointing that out. Indeed it wouldn't make sense for `char**` to decay to `char*[]`, because in general a pointer may not have come from an array. – Arthur Tacca Feb 04 '20 at 11:46
2

For what it's worth ... Taking it completely back to your original problem of not being able to use char** with pybind11, a full working example, scavenged from the pieces you posted is below. Yes, it's not pretty, but working with pointers never is.

#include <pybind11/pybind11.h>
#include <iostream>

#if PY_VERSION_HEX < 0x03000000
#define MyPyText_AsString PyString_AsString
#else
#define MyPyText_AsString PyUnicode_AsUTF8
#endif

namespace py = pybind11;

void closed_func(int argc, char** argv){
    for (int i = 0; i < argc; ++i) {
        std::cout << "FROM C++: " << argv[i] << std::endl;
    }
}

void closed_func_wrap(py::object pyargv11) {
    int argc = 0;
    std::unique_ptr<char*[]> argv;

// convert input list to C/C++ argc/argv
    PyObject* pyargv = pyargv11.ptr();
    if (PySequence_Check(pyargv)) {
        Py_ssize_t sz = PySequence_Size(pyargv);
        argc = (int)sz;
        argv = std::unique_ptr<char*[]>{new char*[sz]};
        for (Py_ssize_t i = 0; i < sz; ++i) {
            PyObject* item = PySequence_GetItem(pyargv, i);
            argv[i] = (char*)MyPyText_AsString(item);
            Py_DECREF(item);
            if (!argv[i] || PyErr_Occurred()) {
                argv = nullptr;
                break;
            }
        }
    }

// bail if failed to convert
    if (!argv) {
        std::cerr << "argument is not a sequence of strings" << std::endl;
        return;
    }

// call the closed function with the proper types
    closed_func(argc, argv.get());
}

PYBIND11_MODULE(HelloEposCmd, m)
{
    m.def("run", &closed_func_wrap, "runs the HelloEposCmd");
}

Which after compiling can be used as expected:

$ python - a b c d=13
>>> import HelloEposCmd
>>> import sys
>>> HelloEposCmd.run(sys.argv)
FROM C++: -
FROM C++: a
FROM C++: b
FROM C++: c
FROM C++: d=13
>>> 
Wim Lavrijsen
  • 3,453
  • 1
  • 9
  • 21