PyBind11: How to implement Vector of Shared Pointers of Classes with Inheritence? Unable to load a custom holder type from a default-holder instance

Question:

I have a base class in c++ that looks like:

class EdgeAttributePipelineStep {
public:
    virtual void setAttributes(Edge& edge) = 0;
};

And then some classes that inherit from this class:

class FuelCostStep : public EdgeAttributePipelineStep {
public:
    FuelCostStep(float fuelCostPerTonne)
        : m_FuelCostPerTonne(fuelCostPerTonne) {};
    void setAttributes(Edge& edge);

private:
    float m_FuelCostPerTonne;
};

class HireCostStep : public EdgeAttributePipelineStep {
public:
    HireCostStep(float hireCostPerDay) : m_HireCostPerHour(hireCostPerDay/24) {};
    void setAttributes(Edge &edge);
private:
    float m_HireCostPerHour;
};

And then finally, a class that accepts a vector of the base class in its constructor:

class EdgeAttributePipeline {
public:
    EdgeAttributePipeline(std::vector<std::shared_ptr<EdgeAttributePipelineStep>> steps);

private:
    std::vector<std::shared_ptr<EdgeAttributePipelineStep>> m_steps;
};

I want to be able to create the pipeline class in python like:

pipeline = EdgeAttributePipeline([
    FuelCostStep(10),
    HireCostStep(10)
])

And I attempted some python bindings using PyBind11 to do this:

#include "edge_attributes.h"
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

PYBIND11_MODULE(WeatherRouting, m)
{
    py::class_<EdgeAttributePipelineStep>(m, "EdgeAttribtuePipelineStep");
    // I tried both using std::shared_ptr
    py::class_<FuelCostStep, std::shared_ptr<FuelCostStep>>(m, "FuelCostStep")
        .def(py::init<double>());
    // And not using std::shared_ptr
    py::class_<HireCostStep, EdgeAttributePipelineStep>(m, "HireCostStep")
        .def(py::init<double>());
    py::class_<EdgeAttributePipeline>(m, "EdgeAttributePipeline")
        .def(py::init<std::vector<std::shared_ptr<EdgeAttributePipelineStep>>>());
}

However both of these attempts resulted in the same error:

>>> EdgeAttributePipeline([FuelCostStep(10)])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: Unable to load a custom holder type from a default-holder instance
>>> EdgeAttributePipeline([HireCostStep(10)])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: Unable to load a custom holder type from a default-holder instance

I also had a look at making a trampoline class:

class PyEdgeAttributePipelineStep : public EdgeAttributePipelineStep {
public:
    using EdgeAttributePipelineStep::EdgeAttributePipelineStep;
};

And modifying the pybind code:

    py::class_<EdgeAttributePipelineStep, PyEdgeAttributePipelineStep, std::shared_ptr<EdgeAttributePipelineStep>>(m, "EdgeAttribtuePipelineStep");
    py::class_<FuelCostStep, EdgeAttributePipelineStep>(m, "FuelCostStep")
        .def(py::init<double>());

However this now makes a new error when ran in python:

ImportError: generic_type: type "FuelCostStep" does not have a non-default holder type while its base "EdgeAttributePipelineStep" does

I am a bit stuck trying to implement this. How do I get this code to work?

Asked By: Tom McLean

||

Answers:

You need to declare the same holder for all related types (so the base, child, grandchild, etc). You forgot to set a shared_ptr holder for HireCostStep.

FWIW, I use the smart_holder branch of pybind11 and it removes all of this holder related pain. I don’t understand why it isn’t in master by now.

Answered By: Dustin Spicuzza
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.