1

I am trying to simplify generating wrapper classes in Pybind11 for a C++ template class. Here is a minimal example (following this answer) showing the problem:

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

namespace py = pybind11;

template<class T>
class Foo {
public:
    Foo(T bar) : bar_(bar) {}
    void print() {
        std::cout << "Type id: " << typeid(T).name() << '\n';
    }
private:
    T bar_;
};

PYBIND11_MODULE(example, m) {
    template<typename T>
    void declare_foo(py::module &m, std::string &typestr) {
        using Class = Foo<T>;
        std::string pyclass_name = std::string("Foo") + typestr;
        py::class_<Class>(m, pyclass_name.c_str())
            .def(py::init< T >())
            .def("print", &Class::print);
    }
    declare_foo<int>(m, "Int");
    declare_foo<double>(m, "Double");
    # More similar declarations follow here...
}

When I compile this with:

g++ -O3 -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` example.cpp -o example`python3-config --extension-suffix`

I get error:

example.cpp: In function ‘void pybind11_init_example(pybind11::module&)’:
example.cpp:18:5: error: a template declaration cannot appear at block scope
   18 |     template<typename T>
      |     ^~~~~~~~
Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174

2 Answers2

2

Like the error suggests you cannot have a template declaration inside a block scope (You are clearly in one https://en.cppreference.com/w/cpp/language/scope). Simply move it outside and capture string parameter by const reference (or value).

Changing the code to

template<typename T>
void declare_foo(py::module &m, const std::string &typestr) {
    using Class = Foo<T>;
    std::string pyclass_name = std::string("Foo") + typestr;
    py::class_<Class>(m, pyclass_name.c_str())
        .def(py::init< T >())
        .def("print", &Class::print);
}

PYBIND11_MODULE(example, m) {
    declare_foo<int>(m, "Int");
    declare_foo<double>(m, "Double");
}

works.

As a side note, you should also

#include <string>

It is not recommended to rely on transitive includes.

Empty Space
  • 743
  • 6
  • 17
1

I came up with the following workaround using macro

template<class T>
class Foo {
public:
    Foo(T bar) : bar_(bar) {}
    void print() {
        std::cout << "Type id: " << typeid(T).name() << '\n';
    }
private:
    T bar_;
};

#define DECLARE_FOO(T, NAME) { \
  py::class_<Foo<T> >(m, (std::string("Foo")+NAME).c_str()) \
    .def(py::init< T >()) \
    .def("print", &Foo<T>::print); \
}

PYBIND11_MODULE(example, m) {
  DECLARE_FOO(int, "int");
  DECLARE_FOO(float, "float");
}

It seems to be working, however I am not sure if this macro is robust enough.

pptaszni
  • 5,591
  • 5
  • 27
  • 43