3

I would like to bind a C++-function with PyBind11. The problem is that this functions has an argument with a double pointer and the compiler raises an error

error: cannot initialize a parameter of type 'char **' with an rvalue of type 'typename make_caster<char **>::cast_op_type<typename std::add_rvalue_reference<char**>::type>' (aka 'char *').

Specifically the code look like this:

#include <pybind11/pybind11.h>

#include <iostream>

namespace py = pybind11;

void parse_args(int argn__, char** argv__)
{
  for(int i = 1; i < argn__; ++i)
    {
      std::cout<< argv__[i];
    }
}

PYBIND11_MODULE(argv_bind, m) {
   m.def("parse_args", &parse_args);     
}
Quasar
  • 295
  • 3
  • 13
  • Probably pybind11 can't decide what python class is equivalent to the double pointer. How would the python code provide the double pointer to the c++ extension? The python side can instead give a numpy array or python list, which are translated to `py::array`, Eigen array, or `std::list` at the c++ side. By the way, python's `sys.argv` and `argparse` already can parse command line arguments. You can use those and pass the result to c++ extension by `bool`, `std::string`, `int`, etc. Since parsing commandline arguments is not performance critical, python can do that. – R zu Mar 10 '18 at 15:15

2 Answers2

8

For non-simple argument types (e.g. like your char **) you'll have to wrap the function itself, where your wrapper provides the logic to go from a simpler type or a Python object to the desired type.

In this case, char * are actually a pretty unsafe type to work with, since it doesn't get memory cleanup; to make this work robustly you'll need to take std::strings, from which you can then access the char * via the c_str() method. (In pybind11, even when you take char * arguments, those are really just pointers into std::string arguments: the Python API deliberately doesn't let callers access the internal storage of Python strings).

So then you just want to combine it with a type that can itself expose the whole thing as a char **, which is pretty much what std::vector is designed to do. Here's a wrapper that would fit your example code:

#include <pybind11/stl.h>

m.def("parse_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()));
    return parse_args(cstrs.size(), cstrs.data());
});

(If your parse_args actually takes a const char ** you can drop the const_cast).

Jason Rhinelander
  • 1,264
  • 12
  • 8
  • I would appreciate if you could take a look at my question [here](https://stackoverflow.com/a/60039323/4999991) as well. thanks in advance. – Foad S. Farimani Feb 03 '20 at 13:22
1

Actually, Jason's answer could cause undefined behavior because it is taking away the const-ness of c_str() with the const_char, i.e., const_cast<char *>(s.c_str()). So in his case cstrs should really be std::vector<const char*> so the const_char can be removed.

However, parse_args usually expects char** rather than const char* const*. Thus, a more correct alternative would be:

#include <pybind11/stl.h>

m.def("parse_args", [](std::vector<std::string> args) {
    std::vector<char *> cstrs;
    cstrs.reserve(args.size());
    for (std::vector& s : args) cstrs.push_back(&s[0]); // s.data() valid since C++11
    return parse_args(cstrs.size(), cstrs.data());
});
Ginés Hidalgo
  • 717
  • 9
  • 20